aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--Gemfile15
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md3
-rw-r--r--RELEASING_RAILS.rdoc33
-rw-r--r--Rakefile36
-rw-r--r--actionmailer/CHANGELOG.md40
-rw-r--r--actionmailer/README.rdoc4
-rw-r--r--actionmailer/lib/action_mailer/base.rb55
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb15
-rw-r--r--actionmailer/lib/action_mailer/preview.rb43
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb11
-rw-r--r--actionmailer/test/base_test.rb60
-rw-r--r--actionmailer/test/delivery_methods_test.rb29
-rw-r--r--actionpack/CHANGELOG.md456
-rw-r--r--actionpack/RUNNING_UNIT_TESTS.rdoc10
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb64
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb9
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb11
-rw-r--r--actionpack/lib/action_controller/metal.rb3
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb14
-rw-r--r--actionpack/lib/action_controller/metal/live.rb56
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb28
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb15
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb43
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb8
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb4
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb1
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb4
-rw-r--r--actionpack/lib/action_controller/test_case.rb64
-rw-r--r--actionpack/lib/action_dispatch.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb40
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb17
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb44
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/simulator.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb11
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb68
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb132
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb27
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb)0
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb182
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb115
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb11
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb8
-rw-r--r--actionpack/lib/action_pack/gem_version.rb15
-rw-r--r--actionpack/lib/action_pack/version.rb11
-rw-r--r--actionpack/test/controller/assert_select_test.rb24
-rw-r--r--actionpack/test/controller/caching_test.rb35
-rw-r--r--actionpack/test/controller/filters_test.rb16
-rw-r--r--actionpack/test/controller/flash_hash_test.rb10
-rw-r--r--actionpack/test/controller/flash_test.rb31
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb2
-rw-r--r--actionpack/test/controller/live_stream_test.rb85
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb11
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb30
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb170
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb190
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb168
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb36
-rw-r--r--actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb30
-rw-r--r--actionpack/test/controller/parameters/parameters_require_test.rb10
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb20
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb28
-rw-r--r--actionpack/test/controller/required_params_test.rb8
-rw-r--r--actionpack/test/controller/test_case_test.rb31
-rw-r--r--actionpack/test/controller/url_for_test.rb3
-rw-r--r--actionpack/test/dispatch/cookies_test.rb269
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb38
-rw-r--r--actionpack/test/dispatch/live_response_test.rb15
-rw-r--r--actionpack/test/dispatch/mount_test.rb5
-rw-r--r--actionpack/test/dispatch/rack_test.rb191
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb7
-rw-r--r--actionpack/test/dispatch/request/session_test.rb41
-rw-r--r--actionpack/test/dispatch/request_test.rb579
-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_test.rb459
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb3
-rw-r--r--actionpack/test/dispatch/ssl_test.rb7
-rw-r--r--actionpack/test/dispatch/static_test.rb9
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb3
-rw-r--r--actionpack/test/journey/router/utils_test.rb8
-rw-r--r--actionpack/test/journey/router_test.rb13
-rw-r--r--actionview/CHANGELOG.md291
-rw-r--r--actionview/lib/action_view.rb5
-rw-r--r--actionview/lib/action_view/base.rb4
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb92
-rw-r--r--actionview/lib/action_view/digestor.rb70
-rw-r--r--actionview/lib/action_view/gem_version.rb15
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb29
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/csrf_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb47
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb138
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb19
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb21
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb29
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb10
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb10
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/routing_url_for.rb8
-rw-r--r--actionview/lib/action_view/template.rb4
-rw-r--r--actionview/lib/action_view/template/html.rb34
-rw-r--r--actionview/lib/action_view/template/resolver.rb19
-rw-r--r--actionview/lib/action_view/template/text.rb2
-rw-r--r--actionview/lib/action_view/test_case.rb3
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb12
-rw-r--r--actionview/lib/action_view/version.rb11
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb9
-rw-r--r--actionview/test/fixtures/customers/_customer.xml.erb1
-rw-r--r--actionview/test/fixtures/digestor/messages/new.html+iphone.erb15
-rw-r--r--actionview/test/fixtures/test/hello_world.html+phone.erb1
-rw-r--r--actionview/test/fixtures/test/hello_world.text+phone.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb6
-rw-r--r--actionview/test/template/date_helper_test.rb26
-rw-r--r--actionview/test/template/dependency_tracker_test.rb114
-rw-r--r--actionview/test/template/digestor_test.rb53
-rw-r--r--actionview/test/template/form_collections_helper_test.rb58
-rw-r--r--actionview/test/template/form_helper_test.rb194
-rw-r--r--actionview/test/template/form_options_helper_test.rb22
-rw-r--r--actionview/test/template/form_tag_helper_test.rb18
-rw-r--r--actionview/test/template/html_test.rb17
-rw-r--r--actionview/test/template/lookup_context_test.rb14
-rw-r--r--actionview/test/template/number_helper_test.rb42
-rw-r--r--actionview/test/template/render_test.rb12
-rw-r--r--actionview/test/template/text_test.rb17
-rw-r--r--actionview/test/template/translation_helper_test.rb10
-rw-r--r--actionview/test/template/url_helper_test.rb45
-rw-r--r--activemodel/CHANGELOG.md46
-rw-r--r--activemodel/README.rdoc79
-rw-r--r--activemodel/lib/active_model.rb5
-rw-r--r--activemodel/lib/active_model/conversion.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb8
-rw-r--r--activemodel/lib/active_model/errors.rb9
-rw-r--r--activemodel/lib/active_model/gem_version.rb15
-rw-r--r--activemodel/lib/active_model/secure_password.rb30
-rw-r--r--activemodel/lib/active_model/validations.rb24
-rw-r--r--activemodel/lib/active_model/validations/absence.rb2
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb6
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb6
-rw-r--r--activemodel/lib/active_model/validations/format.rb6
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb6
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb4
-rw-r--r--activemodel/lib/active_model/validations/with.rb4
-rw-r--r--activemodel/lib/active_model/version.rb11
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb76
-rw-r--r--activemodel/test/cases/conversion_test.rb10
-rw-r--r--activemodel/test/cases/dirty_test.rb27
-rw-r--r--activemodel/test/cases/errors_test.rb14
-rw-r--r--activemodel/test/cases/secure_password_test.rb197
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb48
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb66
-rw-r--r--activemodel/test/cases/translation_test.rb4
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb39
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb8
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb5
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/validates_test.rb6
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb17
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb3
-rw-r--r--activemodel/test/cases/validations_test.rb23
-rw-r--r--activemodel/test/models/automobile.rb3
-rw-r--r--activemodel/test/models/oauthed_user.rb11
-rw-r--r--activemodel/test/models/user.rb2
-rw-r--r--activerecord/CHANGELOG.md1567
-rw-r--r--activerecord/lib/active_record/association_relation.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb55
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb68
-rw-r--r--activerecord/lib/active_record/associations/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb139
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb29
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb89
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb61
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb16
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb27
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb36
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb37
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb36
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb35
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb39
-rw-r--r--activerecord/lib/active_record/autosave_association.rb15
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb95
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb103
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb58
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb109
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb158
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb57
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb202
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb342
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_handling.rb43
-rw-r--r--activerecord/lib/active_record/core.rb109
-rw-r--r--activerecord/lib/active_record/counter_cache.rb49
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb8
-rw-r--r--activerecord/lib/active_record/enum.rb84
-rw-r--r--activerecord/lib/active_record/errors.rb10
-rw-r--r--activerecord/lib/active_record/fixtures.rb40
-rw-r--r--activerecord/lib/active_record/gem_version.rb15
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb12
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb7
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/null_relation.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb37
-rw-r--r--activerecord/lib/active_record/querying.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb23
-rw-r--r--activerecord/lib/active_record/railties/databases.rake9
-rw-r--r--activerecord/lib/active_record/reflection.rb30
-rw-r--r--activerecord/lib/active_record/relation.rb54
-rw-r--r--activerecord/lib/active_record/relation/batches.rb33
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb10
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb146
-rw-r--r--activerecord/lib/active_record/relation/merger.rb11
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb167
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb3
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb12
-rw-r--r--activerecord/lib/active_record/scoping.rb5
-rw-r--r--activerecord/lib/active_record/scoping/default.rb15
-rw-r--r--activerecord/lib/active_record/scoping/named.rb6
-rw-r--r--activerecord/lib/active_record/statement_cache.rb84
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/lib/active_record/transactions.rb16
-rw-r--r--activerecord/lib/active_record/validations.rb4
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb10
-rw-r--r--activerecord/lib/active_record/version.rb11
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb43
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb108
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb80
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb53
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb98
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb50
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb76
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb57
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb35
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb279
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb117
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb53
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb158
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb32
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb388
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb10
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb74
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb27
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb203
-rw-r--r--activerecord/test/cases/associations/eager_test.rb52
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb39
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb214
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb38
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb17
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb9
-rw-r--r--activerecord/test/cases/associations_test.rb9
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb71
-rw-r--r--activerecord/test/cases/autosave_association_test.rb116
-rw-r--r--activerecord/test/cases/base_test.rb57
-rw-r--r--activerecord/test/cases/batches_test.rb41
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb14
-rw-r--r--activerecord/test/cases/calculations_test.rb36
-rw-r--r--activerecord/test/cases/column_definition_test.rb8
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb62
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb54
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb127
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb192
-rw-r--r--activerecord/test/cases/connection_management_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb32
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb27
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/disconnected_test.rb2
-rw-r--r--activerecord/test/cases/enum_test.rb194
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/explain_test.rb8
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb5
-rw-r--r--activerecord/test/cases/finder_test.rb166
-rw-r--r--activerecord/test/cases/fixtures_test.rb23
-rw-r--r--activerecord/test/cases/helper.rb16
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb6
-rw-r--r--activerecord/test/cases/integration_test.rb18
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb2
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb33
-rw-r--r--activerecord/test/cases/locking_test.rb11
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb3
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb3
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb6
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb3
-rw-r--r--activerecord/test/cases/migration/index_test.rb3
-rw-r--r--activerecord/test/cases/migration/logger_test.rb3
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb3
-rw-r--r--activerecord/test/cases/migration_test.rb62
-rw-r--r--activerecord/test/cases/migrator_test.rb5
-rw-r--r--activerecord/test/cases/mixin_test.rb6
-rw-r--r--activerecord/test/cases/modules_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb42
-rw-r--r--activerecord/test/cases/persistence_test.rb26
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb6
-rw-r--r--activerecord/test/cases/query_cache_test.rb15
-rw-r--r--activerecord/test/cases/reaper_test.rb20
-rw-r--r--activerecord/test/cases/reflection_test.rb12
-rw-r--r--activerecord/test/cases/relation/merging_test.rb35
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb25
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb93
-rw-r--r--activerecord/test/cases/relation_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb191
-rw-r--r--activerecord/test/cases/result_test.rb12
-rw-r--r--activerecord/test/cases/sanitize_test.rb32
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb13
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb28
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb70
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb5
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb3
-rw-r--r--activerecord/test/cases/statement_cache_test.rb64
-rw-r--r--activerecord/test/cases/store_test.rb24
-rw-r--r--activerecord/test/cases/test_case.rb8
-rw-r--r--activerecord/test/cases/timestamp_test.rb49
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb172
-rw-r--r--activerecord/test/cases/transactions_test.rb17
-rw-r--r--activerecord/test/cases/unconnected_test.rb2
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb43
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb47
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations_test.rb15
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb1
-rw-r--r--activerecord/test/fixtures/companies.yml8
-rw-r--r--activerecord/test/fixtures/computers.yml1
-rw-r--r--activerecord/test/fixtures/pirates.yml3
-rw-r--r--activerecord/test/fixtures/topics.yml7
-rw-r--r--activerecord/test/fixtures/uuid_children.yml3
-rw-r--r--activerecord/test/fixtures/uuid_parents.yml2
-rw-r--r--activerecord/test/models/book.rb1
-rw-r--r--activerecord/test/models/car.rb1
-rw-r--r--activerecord/test/models/category.rb1
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/college.rb5
-rw-r--r--activerecord/test/models/column.rb3
-rw-r--r--activerecord/test/models/developer.rb2
-rw-r--r--activerecord/test/models/electron.rb2
-rw-r--r--activerecord/test/models/mixed_case_monkey.rb2
-rw-r--r--activerecord/test/models/molecule.rb2
-rw-r--r--activerecord/test/models/movie.rb2
-rw-r--r--activerecord/test/models/owner.rb17
-rw-r--r--activerecord/test/models/person.rb13
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/models/post.rb10
-rw-r--r--activerecord/test/models/reader.rb2
-rw-r--r--activerecord/test/models/record.rb2
-rw-r--r--activerecord/test/models/student.rb1
-rw-r--r--activerecord/test/models/tag.rb4
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/models/uuid_child.rb3
-rw-r--r--activerecord/test/models/uuid_parent.rb3
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb27
-rw-r--r--activerecord/test/support/connection_helper.rb14
-rw-r--r--activerecord/test/support/ddl_helper.rb8
-rw-r--r--activesupport/CHANGELOG.md554
-rw-r--r--activesupport/lib/active_support/cache.rb13
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb44
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb39
-rw-r--r--activesupport/lib/active_support/callbacks.rb18
-rw-r--r--activesupport/lib/active_support/concern.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb15
-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_accessors.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb11
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/gem_version.rb15
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb17
-rw-r--r--activesupport/lib/active_support/inflections.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb13
-rw-r--r--activesupport/lib/active_support/json/encoding.rb8
-rw-r--r--activesupport/lib/active_support/key_generator.rb16
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb8
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb3
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb8
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb2
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb96
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb15
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb6
-rw-r--r--activesupport/lib/active_support/version.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini.rb6
-rw-r--r--activesupport/test/caching_test.rb17
-rw-r--r--activesupport/test/concern_test.rb10
-rw-r--r--activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb11
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb12
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb34
-rw-r--r--activesupport/test/core_ext/duration_test.rb7
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb25
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb42
-rw-r--r--activesupport/test/core_ext/kernel/concern_test.rb12
-rw-r--r--activesupport/test/core_ext/kernel_test.rb2
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb58
-rw-r--r--activesupport/test/core_ext/module_test.rb10
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb (renamed from activesupport/test/core_ext/deep_dup_test.rb)0
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb (renamed from activesupport/test/core_ext/duplicable_test.rb)25
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb5
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb13
-rw-r--r--activesupport/test/core_ext/securerandom_test.rb28
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb44
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb34
-rw-r--r--activesupport/test/dependencies_test.rb12
-rw-r--r--activesupport/test/inflector_test.rb1
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb120
-rw-r--r--activesupport/test/multibyte_conformance_test.rb (renamed from activesupport/test/multibyte_conformance.rb)4
-rw-r--r--activesupport/test/number_helper_test.rb3
-rw-r--r--activesupport/test/ordered_hash_test.rb5
-rw-r--r--activesupport/test/test_test.rb16
-rw-r--r--activesupport/test/time_zone_test.rb29
-rw-r--r--activesupport/test/xml_mini_test.rb183
-rw-r--r--guides/CHANGELOG.md14
-rw-r--r--guides/Rakefile4
-rw-r--r--guides/assets/images/getting_started/article_with_comments.pngbin0 -> 15190 bytes
-rw-r--r--guides/assets/images/getting_started/challenge.pngbin31976 -> 21690 bytes
-rw-r--r--guides/assets/images/getting_started/confirm_dialog.pngbin29542 -> 18809 bytes
-rw-r--r--guides/assets/images/getting_started/forbidden_attributes_for_new_article.pngbin0 -> 10783 bytes
-rw-r--r--guides/assets/images/getting_started/forbidden_attributes_for_new_post.pngbin19490 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/form_with_errors.pngbin14031 -> 12447 bytes
-rw-r--r--guides/assets/images/getting_started/index_action_with_edit_link.pngbin9772 -> 10209 bytes
-rw-r--r--guides/assets/images/getting_started/new_article.pngbin0 -> 3579 bytes
-rw-r--r--guides/assets/images/getting_started/new_post.pngbin5090 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/post_with_comments.pngbin18496 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/rails_welcome.jpgbin293182 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/rails_welcome.pngbin0 -> 94542 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_controller.pngbin5519 -> 4186 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_route_matches.pngbin6195 -> 5913 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_articles.pngbin0 -> 2965 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_posts.pngbin2991 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/template_is_missing_articles_new.pngbin0 -> 6174 bytes
-rw-r--r--guides/assets/images/getting_started/template_is_missing_posts_new.pngbin11688 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/undefined_method_post_path.pngbin9217 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_articles.pngbin0 -> 5327 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_posts.pngbin6852 -> 0 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_new_for_articles.pngbin0 -> 5481 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_new_for_posts.pngbin6998 -> 0 bytes
-rw-r--r--guides/code/getting_started/Gemfile4
-rw-r--r--guides/code/getting_started/Rakefile2
-rw-r--r--guides/code/getting_started/app/views/layouts/application.html.erb4
-rw-r--r--guides/code/getting_started/config.ru2
-rw-r--r--guides/code/getting_started/config/environment.rb2
-rw-r--r--guides/code/getting_started/config/environments/development.rb2
-rw-r--r--guides/code/getting_started/config/environments/production.rb4
-rw-r--r--guides/code/getting_started/config/environments/test.rb4
-rw-r--r--guides/code/getting_started/config/initializers/secret_token.rb2
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb2
-rw-r--r--guides/code/getting_started/config/routes.rb2
-rw-r--r--guides/rails_guides/helpers.rb2
-rw-r--r--guides/source/3_0_release_notes.md4
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_1_release_notes.md134
-rw-r--r--guides/source/_welcome.html.erb7
-rw-r--r--guides/source/action_controller_overview.md97
-rw-r--r--guides/source/action_mailer_basics.md10
-rw-r--r--guides/source/action_view_overview.md17
-rw-r--r--guides/source/active_record_callbacks.md3
-rw-r--r--guides/source/active_record_querying.md97
-rw-r--r--guides/source/active_record_validations.md20
-rw-r--r--guides/source/active_support_core_extensions.md55
-rw-r--r--guides/source/active_support_instrumentation.md1
-rw-r--r--guides/source/api_documentation_guidelines.md59
-rw-r--r--guides/source/asset_pipeline.md76
-rw-r--r--guides/source/association_basics.md15
-rw-r--r--guides/source/caching_with_rails.md24
-rw-r--r--guides/source/command_line.md10
-rw-r--r--guides/source/configuring.md67
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md249
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/debugging_rails_applications.md686
-rw-r--r--guides/source/development_dependencies_install.md15
-rw-r--r--guides/source/documents.yaml3
-rw-r--r--guides/source/engines.md10
-rw-r--r--guides/source/form_helpers.md39
-rw-r--r--guides/source/getting_started.md1190
-rw-r--r--guides/source/i18n.md125
-rw-r--r--guides/source/initialization.md165
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/layouts_and_rendering.md47
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/migrations.md163
-rw-r--r--guides/source/nested_model_forms.md6
-rw-r--r--guides/source/plugins.md6
-rw-r--r--guides/source/rails_on_rack.md9
-rw-r--r--guides/source/routing.md44
-rw-r--r--guides/source/security.md81
-rw-r--r--guides/source/testing.md21
-rw-r--r--guides/source/upgrading_ruby_on_rails.md181
-rw-r--r--guides/source/working_with_javascript_in_rails.md6
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md295
-rw-r--r--railties/lib/rails.rb4
-rw-r--r--railties/lib/rails/app_rails_loader.rb2
-rw-r--r--railties/lib/rails/application.rb44
-rw-r--r--railties/lib/rails/application/bootstrap.rb6
-rw-r--r--railties/lib/rails/application/configuration.rb7
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb7
-rw-r--r--railties/lib/rails/commands/console.rb36
-rw-r--r--railties/lib/rails/commands/plugin.rb2
-rw-r--r--railties/lib/rails/commands/server.rb13
-rw-r--r--railties/lib/rails/engine.rb3
-rw-r--r--railties/lib/rails/gem_version.rb15
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb68
-rw-r--r--railties/lib/rails/generators/app_base.rb116
-rw-r--r--railties/lib/rails/generators/erb.rb4
-rw-r--r--railties/lib/rails/generators/erb/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.text.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb9
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb4
-rw-r--r--railties/lib/rails/generators/migration.rb38
-rw-r--r--railties/lib/rails/generators/model_helpers.rb28
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml27
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/model/model_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile8
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt4
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb17
-rw-r--r--railties/lib/rails/info.rb2
-rw-r--r--railties/lib/rails/paths.rb2
-rw-r--r--railties/lib/rails/rack.rb2
-rw-r--r--railties/lib/rails/railtie.rb22
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb39
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_help.rb1
-rw-r--r--railties/lib/rails/test_unit/railtie.rb2
-rw-r--r--railties/lib/rails/version.rb12
-rw-r--r--railties/test/app_rails_loader_test.rb10
-rw-r--r--railties/test/application/assets_test.rb15
-rw-r--r--railties/test/application/configuration_test.rb134
-rw-r--r--railties/test/application/initializers/frameworks_test.rb4
-rw-r--r--railties/test/application/multiple_applications_test.rb26
-rw-r--r--railties/test/application/rake/dbs_test.rb81
-rw-r--r--railties/test/application/rake/migrations_test.rb31
-rw-r--r--railties/test/application/rake/notes_test.rb125
-rw-r--r--railties/test/application/rake_test.rb11
-rw-r--r--railties/test/commands/console_test.rb32
-rw-r--r--railties/test/generators/app_generator_test.rb117
-rw-r--r--railties/test/generators/create_migration_test.rb134
-rw-r--r--railties/test/generators/generator_test.rb1
-rw-r--r--railties/test/generators/mailer_generator_test.rb8
-rw-r--r--railties/test/generators/migration_generator_test.rb50
-rw-r--r--railties/test/generators/model_generator_test.rb7
-rw-r--r--railties/test/generators/plugin_generator_test.rb29
-rw-r--r--railties/test/generators/resource_generator_test.rb6
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb7
-rw-r--r--railties/test/railties/railtie_test.rb8
-rw-r--r--railties/test/version_test.rb12
-rw-r--r--tasks/release.rb50
-rw-r--r--version.rb9
704 files changed, 15744 insertions, 9424 deletions
diff --git a/.travis.yml b/.travis.yml
index 3ddaf86fb2..9e7a449010 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,8 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- - 2.1.0
- - rbx
+ - 2.1.1
+ - rbx-2
- jruby
env:
- "GEM=railties"
@@ -17,7 +17,7 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- - rvm: rbx
+ - rvm: rbx-2
- rvm: jruby
fast_finish: true
notifications:
@@ -35,4 +35,3 @@ notifications:
bundler_args: --path vendor/bundle --without test
services:
- memcached
-
diff --git a/Gemfile b/Gemfile
index 88f3686e95..120c4bca8f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,10 +8,16 @@ gemspec
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'bcrypt-ruby', '~> 3.1.2'
-gem 'jquery-rails', '~> 2.2.0'
+gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
+gem 'arel', github: 'rails/arel'
+gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable'
+
+# require: false so bcrypt is loaded only when has_secure_password is used.
+# This is to avoid ActiveModel (and by extension the entire framework)
+# being dependent on a binary library.
+gem 'bcrypt', '~> 3.1.7', require: false
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
@@ -76,11 +82,6 @@ platforms :jruby do
end
end
-platforms :rbx do
- gem 'psych', '~> 2.0'
- gem 'rubysl', '~> 2.0'
-end
-
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED']
platforms :ruby do
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 78dae579e8..59e61f95df 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.1.0.beta1
+4.2.0.alpha
diff --git a/README.md b/README.md
index fcdcc2d87c..c3577efe53 100644
--- a/README.md
+++ b/README.md
@@ -76,8 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails)
-* [![Dependencies](https://gemnasium.com/rails/rails.png?travis)](https://gemnasium.com/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 664505f60d..c6c1c12e87 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -158,7 +158,7 @@ commits should be added to the release branch besides regression fixing commits.
== Day of release
Many of these steps are the same as for the release candidate, so if you need
-more explanation on a particular step, so the RC steps.
+more explanation on a particular step, see the RC steps.
Today, do this stuff in this order:
@@ -203,34 +203,3 @@ There are two simple steps for fixing the CI:
2. Fix it
Repeat these steps until the CI is green.
-
-=== Manually trigger docs generation
-
-We have a post-receive hook in GitHub that calls the docs server on pushes.
-It triggers generation and publication of edge docs, updates the contrib app,
-and generates and publishes stable docs if a new stable tag is detected.
-
-The hook unfortunately is not invoked by tag pushing, so once the new stable
-tag has been pushed to origin, please run
-
- rake publish_docs
-
-You should see something like this:
-
- Rails master hook tasks scheduled:
-
- * updates the local checkout
- * updates Rails Contributors
- * generates and publishes edge docs
-
- If a new stable tag is detected it also
-
- * generates and publishes stable docs
-
- This needs typically a few minutes.
-
-Note you do not need to specify the tag, the docs server figures it out.
-
-Also, don't worry if you call that multiple times or the hook is triggered
-again by some immediate regular push, if the scripts are running new calls
-are just queued (in a queue of size 1).
diff --git a/Rakefile b/Rakefile
index 07d44fc94b..0737afd089 100644
--- a/Rakefile
+++ b/Rakefile
@@ -45,36 +45,9 @@ else
Rails::API::StableTask.new('rdoc')
end
-desc 'Bump all versions to match version.rb'
-task :update_versions do
- require File.dirname(__FILE__) + "/version"
+desc 'Bump all versions to match RAILS_VERSION'
+task :update_versions => "all:update_versions"
- File.open("RAILS_VERSION", "w") do |f|
- f.puts Rails::VERSION::STRING
- end
-
- constants = {
- "activesupport" => "ActiveSupport",
- "activemodel" => "ActiveModel",
- "actionpack" => "ActionPack",
- "actionview" => "ActionView",
- "actionmailer" => "ActionMailer",
- "activerecord" => "ActiveRecord",
- "railties" => "Rails"
- }
-
- version_file = File.read("version.rb")
-
- PROJECTS.each do |project|
- Dir["#{project}/lib/*/version.rb"].each do |file|
- File.open(file, "w") do |f|
- f.write version_file.gsub(/Rails/, constants[project])
- end
- end
- end
-end
-
-#
# We have a webhook configured in GitHub that gets invoked after pushes.
# This hook triggers the following tasks:
#
@@ -84,11 +57,6 @@ end
# * if there's a new stable tag, generates and publishes stable docs
#
# Everything is automated and you do NOT need to run this task normally.
-#
-# We publish a new version by tagging, and pushing a tag does not trigger
-# that webhook. Stable docs would be updated by any subsequent regular
-# push, but if you want that to happen right away just run this.
-#
desc 'Publishes docs, run this AFTER a new stable tag has been pushed'
task :publish_docs do
Net::HTTP.new('api.rubyonrails.org', 8080).start do |http|
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 1867a392eb..6203699405 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,39 +1 @@
-* Add mailer previews feature based on 37 Signals mail_view gem
-
- *Andrew White*
-
-* Calling `mail()` without arguments serves as getter for the current mail
- message and keeps previously set headers.
-
- Fixes #13090.
-
- Example:
-
- class MailerWithCallback < ActionMailer::Base
- after_action :a_callback
-
- def welcome
- mail subject: "subject", to: ["joe@example.com"]
- end
-
- def a_callback
- mail # => returns the current mail message
- end
- end
-
- *Yves Senn*
-
-* Instrument the generation of Action Mailer messages. The time it takes to
- generate a message is written to the log.
-
- *Daniel Schierbeck*
-
-* Invoke mailer defaults as procs only if they are procs, do not convert with
- `to_proc`. That an object is convertible to a proc does not mean it's meant
- to be always used as a proc.
-
- Fixes #11533.
-
- *Alex Tsukernik*
-
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index c3dcd3c3e4..67b64fe469 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -74,7 +74,7 @@ Or you can just chain the methods together like:
== Setting defaults
-It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers email messages have, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
Note that every value you set with this method will get overwritten if you use the same key in your mailer method.
@@ -102,7 +102,7 @@ Example:
)
if email.has_attachments?
- email.attachments.each do |attachment|
+ email.attachments.each do |attachment|
page.attachments.create({
file: attachment, description: email.subject
})
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a30e3e65da..652ce0b3d8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -51,7 +51,7 @@ module ActionMailer
# * <tt>mail</tt> - Allows you to specify email to be sent.
#
# The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
- # will accept (any valid Email header including optional fields).
+ # will accept (any valid email header including optional fields).
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
@@ -94,7 +94,7 @@ module ActionMailer
# Hi <%= @account.name %>,
# Thanks for joining our service! Please check back often.
#
- # You can even use Action Pack helpers in these views. For example:
+ # You can even use Action View helpers in these views. For example:
#
# You got a new note!
# <%= truncate(@note.body, length: 25) %>
@@ -154,7 +154,7 @@ module ActionMailer
# * signup_notification.text.erb
# * signup_notification.html.erb
# * signup_notification.xml.builder
- # * signup_notification.yaml.erb
+ # * signup_notification.yml.erb
#
# Each would be rendered and added as a separate part to the message, with the corresponding content
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -325,11 +325,26 @@ module ActionMailer
# directory can be configured using the <tt>preview_path</tt> option which has a default
# of <tt>test/mailers/previews</tt>:
#
- # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
#
# An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
# on a running development server instance.
#
+ # Previews can also be intercepted in a similar manner as deliveries can be by registering
+ # a preview interceptor that has a <tt>previewing_email</tt> method:
+ #
+ # class CssInlineStyler
+ # def self.previewing_email(message)
+ # # inline CSS styles
+ # end
+ # end
+ #
+ # config.action_mailer.register_preview_interceptor :css_inline_styler
+ #
+ # Note that interceptors need to be registered both with <tt>register_interceptor</tt>
+ # and <tt>register_preview_interceptor</tt> if they should operate on both sending and
+ # previewing emails.
+ #
# = Configuration options
#
# These options are specified on the class level, like
@@ -429,18 +444,30 @@ module ActionMailer
end
# Register an Observer which will be notified when mail is delivered.
- # Either a class or a string can be passed in as the Observer. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # Either a class, string or symbol can be passed in as the Observer.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_observer(observer)
- delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
+ delivery_observer = case observer
+ when String, Symbol
+ observer.to_s.camelize.constantize
+ else
+ observer
+ end
+
Mail.register_observer(delivery_observer)
end
# Register an Interceptor which will be called before mail is sent.
- # Either a class or a string can be passed in as the Interceptor. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # Either a class, string or symbol can be passed in as the Interceptor.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_interceptor(interceptor)
- delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
+ delivery_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
Mail.register_interceptor(delivery_interceptor)
end
@@ -697,11 +724,11 @@ module ActionMailer
# format.html
# end
#
- # You can even render text directly without using a template:
+ # You can even render plain text directly without using a template:
#
# mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text { render text: "Hello Mikel!" }
- # format.html { render text: "<h1>Hello Mikel!</h1>" }
+ # format.text { render plain: "Hello Mikel!" }
+ # format.html { render html: "<h1>Hello Mikel!</h1>".html_safe }
# end
#
# Which will render a +multipart/alternative+ email with +text/plain+ and
@@ -737,7 +764,7 @@ module ActionMailer
m.charset = charset = headers[:charset]
# Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
+ wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
# Assign all headers except parts_order, content_type and body
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
new file mode 100644
index 0000000000..b564813ccf
--- /dev/null
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -0,0 +1,15 @@
+module ActionMailer
+ # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index ecceaf8c70..33a9faa7e7 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -9,7 +9,32 @@ module ActionMailer
#
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
#
- class_attribute :preview_path, instance_writer: false
+ mattr_accessor :preview_path, instance_writer: false
+
+ # :nodoc:
+ mattr_accessor :preview_interceptors, instance_writer: false
+ self.preview_interceptors = []
+
+ # Register one or more Interceptors which will be called before mail is previewed.
+ def register_preview_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
+ end
+
+ # Register an Interceptor which will be called before mail is previewed.
+ # Either a class or a string can be passed in as the Interceptor. If a
+ # string is passed in it will be <tt>constantize</tt>d.
+ def register_preview_interceptor(interceptor)
+ preview_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
+ unless preview_interceptors.include?(preview_interceptor)
+ preview_interceptors << preview_interceptor
+ end
+ end
end
end
@@ -23,10 +48,14 @@ module ActionMailer
descendants
end
- # Returns the mail object for the given email name
+ # Returns the mail object for the given email name. The registered preview
+ # interceptors will be informed so that they can transform the message
+ # as they would if the mail was actually being delivered.
def call(email)
preview = self.new
- preview.public_send(email)
+ message = preview.public_send(email)
+ inform_preview_interceptors(message)
+ message
end
# Returns all of the available email previews
@@ -56,7 +85,7 @@ module ActionMailer
protected
def load_previews #:nodoc:
- if preview_path?
+ if preview_path
Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
end
end
@@ -65,8 +94,10 @@ module ActionMailer
Base.preview_path
end
- def preview_path? #:nodoc:
- Base.preview_path?
+ def inform_preview_interceptors(message) #:nodoc:
+ Base.preview_interceptors.each do |interceptor|
+ interceptor.previewing_email(message)
+ end
end
end
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index c893ddfef5..8d1e40297b 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -46,7 +46,7 @@ module ActionMailer
end
config.after_initialize do
- if ActionMailer::Base.preview_path?
+ if ActionMailer::Base.preview_path
ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 46eb763c26..a98aec913f 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActionMailer
- # Returns the version of the currently loaded ActionMailer as a Gem::Version
+ # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActionMailer.version.segments
- STRING = ActionMailer.version.to_s
+ gem_version
end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index c1759d9b92..b66e5bd6a3 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -13,6 +13,7 @@ class BaseTest < ActiveSupport::TestCase
def teardown
ActionMailer::Base.asset_host = nil
ActionMailer::Base.assets_dir = nil
+ ActionMailer::Base.preview_interceptors.clear
end
test "method call to mail does not raise error" do
@@ -530,6 +531,13 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
+ ActionMailer::Base.register_observer(:"base_test/my_observer")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
test "you can register multiple observers to the mail object that both get informed on email delivery" do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
@@ -539,12 +547,18 @@ class BaseTest < ActiveSupport::TestCase
end
class MyInterceptor
- def self.delivering_email(mail)
- end
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
end
class MySecondInterceptor
- def self.delivering_email(mail)
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
+ end
+
+ class BaseMailerPreview < ActionMailer::Preview
+ def welcome
+ BaseMailer.welcome
end
end
@@ -562,6 +576,13 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
@@ -570,6 +591,39 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(MyInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor("BaseTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(:"base_test/my_interceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ MySecondInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome['X-Proc-Method']
yesterday = 1.day.ago
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 20412c7bb2..609903620b 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -38,8 +38,10 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
test "default sendmail settings" do
- settings = {location: '/usr/sbin/sendmail',
- arguments: '-i -t'}
+ settings = {
+ location: '/usr/sbin/sendmail',
+ arguments: '-i -t'
+ }
assert_equal settings, ActionMailer::Base.sendmail_settings
end
end
@@ -138,13 +140,15 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "default delivery options can be overridden per mail instance" do
- settings = { address: "localhost",
- port: 25,
- domain: 'localhost.localdomain',
- user_name: nil,
- password: nil,
- authentication: nil,
- enable_starttls_auto: true }
+ settings = {
+ address: "localhost",
+ port: 25,
+ domain: 'localhost.localdomain',
+ user_name: nil,
+ password: nil,
+ authentication: nil,
+ enable_starttls_auto: true
+ }
assert_equal settings, ActionMailer::Base.smtp_settings
overridden_options = {user_name: "overridden", password: "somethingobtuse"}
mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options)
@@ -164,6 +168,13 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
end
+ test "undefined delivery methods raises errors" do
+ DeliveryMailer.delivery_method = nil
+ assert_raise RuntimeError do
+ DeliveryMailer.welcome.deliver
+ end
+ end
+
test "does not perform deliveries if requested" do
DeliveryMailer.perform_deliveries = false
DeliveryMailer.deliveries.clear
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index dc98fb583c..15833641bb 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,440 +1,82 @@
-* Fix stream closing when sending file with `ActionController::Live` included.
+* Fixed an issue with migrating legacy json cookies.
- Fixes #12381
+ 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.
- *Alessandro Diaferia*
+ 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.
-* Allow an absolute controller path inside a module scope. Fixes #12777.
+ Fixes #14774.
- Example:
+ *Godfrey Chan*
- namespace :foo do
- # will route to BarController without the namespace.
- get '/special', to: '/bar#index'
- end
+* Make URL escaping more consistent:
+ 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
-* Unique the segment keys array for non-optimized url helpers
+ 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.
- In Rails 3.2 you only needed pass an argument for dynamic segment once so
- unique the segment keys array to match the number of args. Since the number
- of args is less than required parts the non-optimized code path is selected.
- This means to benefit from optimized url generation the arg needs to be
- specified as many times as it appears in the path.
+ Fixes #14629, #14636 and #14070.
- Fixes #12808.
+ *Andrew White*, *Edho Arief*
- *Andrew White*
+* Add alias `ActionDispatch::Http::UploadedFile#to_io` to
+ `ActionDispatch::Http::UploadedFile#tempfile`.
-* Show full route constraints in error message
+ *Tim Linquist*
- When an optimized helper fails to generate, show the full route constraints
- in the error message. Previously it would only show the contraints that were
- required as part of the path.
+* Returns null type format when format is not know and controller is using `any`
+ format block.
- Fixes #13592.
+ Fixes #14462.
- *Andrew White*
+ *Rafael Mendonça França*
-* Use a custom route visitor for optimized url generation. Fixes #13349.
+* Improve routing error page with fuzzy matching search.
- *Andrew White*
+ *Winston*
-* Allow engine root relative redirects using an empty string.
+* Only make deeply nested routes shallow when parent is shallow.
- Example:
+ Fixes #14684.
- # application routes.rb
- mount BlogEngine => '/blog'
+ *Andrew White*, *James Coglan*
- # engine routes.rb
- get '/welcome' => redirect('')
+* Append link to bad code to backtrace when exception is SyntaxError.
- This now redirects to the path `/blog`, whereas before it would redirect
- to the application root path. In the case of a path redirect or a custom
- redirect if the path returned contains a host then the path is treated as
- absolute. Similarly for option redirects, if the options hash returned
- contains a `:host` or `:domain` key then the path is treated as absolute.
+ *Boris Kuznetsov*
- Fixes #7977.
-
- *Andrew White*
+* Swapped the parameters of assert_equal in `assert_select` so that the
+ proper values were printed correctly
-* Fix `Encoding::CompatibilityError` when public path is UTF-8
+ Fixes #14422.
- In #5337 we forced the path encoding to ASCII-8BIT to prevent static file handling
- from blowing up before an application has had chance to deal with possibly invalid
- urls. However this has a negative side effect of making it an incompatible encoding
- if the application's public path has UTF-8 characters in it.
+ *Vishal Lal*
- To work around the problem we check to see if the path has a valid encoding once
- it has been unescaped. If it is not valid then we can return early since it will
- not match any file anyway.
+* 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.
- Fixes #13518.
+ Fixes #14388.
*Andrew White*
-* `ActionController::Parameters#permit!` permits hashes in array values.
-
- *Xavier Noria*
-
-* Converts hashes in arrays of unfiltered params to unpermitted params.
-
- Fixes #13382.
-
- *Xavier Noria*
-
-* New config option to opt out of params "deep munging" that was used to
- address security vulnerability CVE-2013-0155. In your app config:
-
- config.action_dispatch.perform_deep_munge = false
-
- Take care to understand the security risk involved before disabling this.
- [Read more.](https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI)
-
- *Bernard Potocki*
-
-* `rake routes` shows routes defined under assets prefix.
-
- *Ryunosuke SATO*
-
-* Extend cross-site request forgery (CSRF) protection to GET requests with
- JavaScript responses, protecting apps from cross-origin `<script>` tags.
-
- *Jeremy Kemper*
-
-* Fix generating a path for engine inside a resources block.
-
- Fixes #8533.
-
- *Piotr Sarnacki*
-
-* Add `Mime::Type.register "text/vcard", :vcf` to the default list of mime types.
-
- *DHH*
-
-* Remove deprecated `ActionController::RecordIdentifier`, use
- `ActionView::RecordIdentifier` instead.
-
- *kennyj*
-
-* Fix regression when using `ActionView::Helpers::TranslationHelper#translate` with
- `options[:raise]`.
-
- This regression was introduced at ec16ba75a5493b9da972eea08bae630eba35b62f.
-
- *Shota Fukumori (sora_h)*
-
-* Introducing Variants
-
- We often want to render different html/json/xml templates for phones,
- tablets, and desktop browsers. Variants make it easy.
-
- The request variant is a specialization of the request format, like `:tablet`,
- `:phone`, or `:desktop`.
-
- You can set the variant in a `before_action`:
-
- request.variant = :tablet if request.user_agent =~ /iPad/
-
- Respond to variants in the action just like you respond to formats:
-
- respond_to do |format|
- format.html do |html|
- html.tablet # renders app/views/projects/show.html+tablet.erb
- html.phone { extra_setup; render ... }
- end
- end
-
- Provide separate templates for each format and variant:
-
- app/views/projects/show.html.erb
- app/views/projects/show.html+tablet.erb
- app/views/projects/show.html+phone.erb
-
- You can also simplify the variants definition using the inline syntax:
-
- respond_to do |format|
- format.js { render "trash" }
- format.html.phone { redirect_to progress_path }
- format.html.none { render "trash" }
- end
-
- Variants also support common `any`/`all` block that formats have.
-
- It works for both inline:
-
- respond_to do |format|
- format.html.any { render text: "any" }
- format.html.phone { render text: "phone" }
- end
-
- and block syntax:
-
- respond_to do |format|
- format.html do |variant|
- variant.any(:tablet, :phablet){ render text: "any" }
- variant.phone { render text: "phone" }
- end
- end
-
- *Łukasz Strzałkowski*
-
-* Fix render of localized templates without an explicit format using wrong
- content header and not passing correct formats to template due to the
- introduction of the `NullType` for mimes.
-
- Templates like `hello.it.erb` were subject to this issue.
-
- Fixes #13064.
-
- *Angelo Capilleri*, *Carlos Antonio da Silva*
-
-* Try to escape each part of a url correctly when using a redirect route.
-
- Fixes #13110.
-
- *Andrew White*
-
-* Better error message for typos in assert_response argument.
-
- When the response type argument to `assert_response` is not a known
- response type, `assert_response` now throws an ArgumentError with a clear
- message. This is intended to help debug typos in the response type.
-
- *Victor Costan*
-
-* Fix formatting for `rake routes` when a section is shorter than a header.
-
- *Sıtkı Bağdat*
-
-* Take a hash with options inside array in `#url_for`.
-
- Example:
-
- url_for [:new, :admin, :post, { param: 'value' }]
- # => http://example.com/admin/posts/new?param=value
-
- *Andrey Ognevsky*
-
-* Add `session#fetch` method
-
- fetch behaves like [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch).
- It returns a value from the hash for the given key.
- If the key can’t be found, there are several options:
-
- * With no other arguments, it will raise an KeyError exception.
- * If a default value is given, then that will be returned.
- * If the optional code block is specified, then that will be run and its result returned.
-
- *Damien Mathieu*
-
-* Don't let strong parameters mutate the given hash via `fetch`
-
- Create a new instance if the given parameter is a `Hash` instead of
- passing it to the `convert_hashes_to_parameters` method since it is
- overriding its default value.
-
- *Brendon Murphy*, *Doug Cole*
-
-* Add `params` option to `button_to` form helper, which renders the given hash
- as hidden form fields.
-
- *Andy Waite*
-
-* Make assets helpers work in the controllers like it works in the views.
-
- Example:
-
- # config/application.rb
- config.asset_host = 'http://mycdn.com'
-
- ActionController::Base.helpers.asset_path('fallback.png')
- # => http://mycdn.com/assets/fallback.png
-
- Fixes #10051.
-
- *Tima Maslyuchenko*
-
-* Respect `SCRIPT_NAME` when using `redirect` with a relative path
-
- Example:
-
- # application routes.rb
- mount BlogEngine => '/blog'
-
- # engine routes.rb
- get '/admin' => redirect('admin/dashboard')
-
- This now redirects to the path `/blog/admin/dashboard`, whereas before it would've
- generated an invalid url because there would be no slash between the host name and
- the path. It also allows redirects to work where the application is deployed to a
- subdirectory of a website.
-
- Fixes #7977.
-
- *Andrew White*
-
-* Fixing repond_with working directly on the options hash
- This fixes an issue where the respond_with worked directly with the given
- options hash, so that if a user relied on it after calling respond_with,
- the hash wouldn't be the same.
-
- Fixes #12029.
-
- *bluehotdog*
-
-* Fix `ActionDispatch::RemoteIp::GetIp#calculate_ip` to only check for spoofing
- attacks if both `HTTP_CLIENT_IP` and `HTTP_X_FORWARDED_FOR` are set.
-
- Fixes #10844.
-
- *Tamir Duberstein*
-
-* Strong parameters should permit nested number as key.
-
- Fixes #12293.
-
- *kennyj*
-
-* Fix regex used to detect URI schemes in `redirect_to` to be consistent with
- RFC 3986.
-
- *Derek Prior*
-
-* Fix incorrect `assert_redirected_to` failure message for protocol-relative
- URLs.
-
- *Derek Prior*
-
-* Fix an issue where router can't recognize downcased url encoding path.
-
- Fixes #12269.
-
- *kennyj*
-
-* Fix custom flash type definition. Misusage of the `_flash_types` class variable
- caused an error when reloading controllers with custom flash types.
-
- Fixes #12057.
-
- *Ricardo de Cillo*
-
-* Do not break params filtering on `nil` values.
-
- Fixes #12149.
-
- *Vasiliy Ermolovich*
-
-* Development mode exceptions are rendered in text format in case of XHR request.
-
- *Kir Shatrov*
-
-* Fix an issue where :if and :unless controller action procs were being run
- before checking for the correct action in the :only and :unless options.
-
- Fixes #11799.
-
- *Nicholas Jakobsen*
-
-* Fix an issue where `assert_dom_equal` and `assert_dom_not_equal` were
- ignoring the passed failure message argument.
-
- Fixes #11751.
-
- *Ryan McGeary*
-
-* Allow REMOTE_ADDR, HTTP_HOST and HTTP_USER_AGENT to be overridden from
- the environment passed into `ActionDispatch::TestRequest.new`.
-
- Fixes #11590.
-
- *Andrew White*
-
-* Fix an issue where Journey was failing to clear the named routes hash when the
- routes were reloaded and since it doesn't overwrite existing routes then if a
- route changed but wasn't renamed it kept the old definition. This was being
- masked by the optimised url helpers so it only became apparent when passing an
- options hash to the url helper.
-
- *Andrew White*
-
-* Skip routes pointing to a redirect or mounted application when generating urls
- using an options hash as they aren't relevant and generate incorrect urls.
-
- Fixes #8018.
-
- *Andrew White*
-
-* Move `MissingHelperError` out of the `ClassMethods` module.
-
- *Yves Senn*
-
-* Fix an issue where rails raise exception about missing helper where it
- should throw `LoadError`. When helper file exists and only loaded file from
- this helper does not exist rails should throw LoadError instead of
- `MissingHelperError`.
-
- *Piotr Niełacny*
-
-* Fix `ActionDispatch::ParamsParser#parse_formatted_parameters` to rewind body input stream on
- parsing json params.
-
- Fixes #11345.
-
- *Yuri Bol*, *Paul Nikitochkin*
-
-* Ignore spaces around delimiter in Set-Cookie header.
-
- *Yamagishi Kazutoshi*
-
-* Remove deprecated Rails application fallback for integration testing, set
- `ActionDispatch.test_app` instead.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `page_cache_extension` config.
-
- *Francesco Rodriguez*
-
-* Remove deprecated constants from Action Controller:
-
- ActionController::AbstractRequest => ActionDispatch::Request
- ActionController::Request => ActionDispatch::Request
- ActionController::AbstractResponse => ActionDispatch::Response
- ActionController::Response => ActionDispatch::Response
- ActionController::Routing => ActionDispatch::Routing
- ActionController::Integration => ActionDispatch::Integration
- ActionController::IntegrationTest => ActionDispatch::IntegrationTest
-
- *Carlos Antonio da Silva*
-
-* Fix `Mime::Type.parse` when bad accepts header is looked up. Previously it
- was setting `request.formats` with an array containing a `nil` value, which
- raised an error when setting the controller formats.
-
- Fixes #10965.
-
- *Becker*
-
-* Merge `:action` from routing scope and assign endpoint if both `:controller`
- and `:action` are present. The endpoint assignment only occurs if there is
- no `:to` present in the options hash so should only affect routes using the
- shorthand syntax (i.e. endpoint is inferred from the path).
-
- Fixes #9856.
-
- *Yves Senn*, *Andrew White*
-
-* Action View extracted from Action Pack.
-
- *Piotr Sarnacki*, *Łukasz Strzałkowski*
+* Make logging of CSRF failures optional (but on by default) with the
+ `log_warning_on_csrf_failure` configuration setting in
+ `ActionController::RequestForgeryProtection`.
-* Fix removing trailing slash for mounted apps.
+ *John Barton*
- Fixes #3215.
+* Fix URL generation in controller tests with request-dependent
+ `default_url_options` methods.
- *Piotr Sarnacki*
+ *Tony Wooster*
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc
index 08767ae133..2f923136d9 100644
--- a/actionpack/RUNNING_UNIT_TESTS.rdoc
+++ b/actionpack/RUNNING_UNIT_TESTS.rdoc
@@ -1,17 +1,17 @@
== Running with Rake
The easiest way to run the unit tests is through Rake. The default task runs
-the entire test suite for all classes. For more information, checkout the
-full array of rake tasks with "rake -T"
+the entire test suite for all classes. For more information, check out the
+full array of rake tasks with "rake -T".
-Rake can be found at http://rake.rubyforge.org
+Rake can be found at http://rake.rubyforge.org.
== Running by hand
-To run a single test suite
+Run a single test suite:
rake test TEST=path/to/test.rb
-which can be further narrowed down to one test:
+Run one test in a test suite:
rake test TEST=path/to/test.rb TESTOPTS="--name=test_something"
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index d6c941832f..69aca308d6 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -178,41 +178,35 @@ module AbstractController
# set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
[:before, :after, :around].each do |callback|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :#{callback}_filter, :#{callback}_action
-
- # Prepend a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
-
- alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
-
- # Skip a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def skip_#{callback}_action(*names) # def skip_before_action(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :skip_#{callback}_filter, :skip_#{callback}_action
-
- # *_action is the same as append_*_action
- alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
- alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
- RUBY_EVAL
+ define_method "#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"#{callback}_filter", :"#{callback}_action"
+
+ define_method "prepend_#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options.merge(:prepend => true))
+ end
+ end
+
+ alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
+
+ # Skip a before, after or around callback. See _insert_callbacks
+ # for details on the allowed parameters.
+ define_method "skip_#{callback}_action" do |*names|
+ _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
+
+ # *_action is the same as append_*_action
+ alias_method :"append_#{callback}_action", :"#{callback}_action" # alias_method :append_before_action, :before_action
+ alias_method :"append_#{callback}_filter", :"#{callback}_action" # alias_method :append_before_filter, :before_action
end
end
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 7be61d94c9..9d10140ed2 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
+require 'action_view'
require 'action_view/view_paths'
require 'set'
@@ -22,7 +23,7 @@ module AbstractController
def render(*args, &block)
options = _normalize_render(*args, &block)
self.response_body = render_to_body(options)
- _process_format(rendered_format) if rendered_format
+ _process_format(rendered_format, options) if rendered_format
self.response_body
end
@@ -97,7 +98,7 @@ module AbstractController
# Process the rendered format.
# :api: private
- def _process_format(format)
+ def _process_format(format, options = {})
end
# Normalize args and options.
@@ -105,7 +106,9 @@ module AbstractController
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
#TODO: remove defined? when we restore AP <=> AV dependency
- options[:variant] = request.variant if defined?(request) && request.variant.present?
+ if defined?(request) && request && request.variant.present?
+ options[:variant] = request.variant
+ end
_normalize_options(options)
options
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c0f10da23a..e6fe6b0b00 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -45,7 +45,7 @@ module ActionController
#
# def server_ip
# location = request.env["SERVER_ADDR"]
- # render text: "This server hosted at #{location}"
+ # render plain: "This server hosted at #{location}"
# end
#
# == Parameters
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 9279d8bcea..b1acca2435 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -50,7 +50,16 @@ module ActionController
def unpermitted_parameters(event)
unpermitted_keys = event.payload[:keys]
- debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
+ debug("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}")
+ end
+
+ def deep_munge(event)
+ message = "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."\
+
+ debug(message)
end
%w(write_fragment read_fragment exist_fragment?
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index b84c9e78c3..0f4cc7a8f5 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -70,7 +70,8 @@ module ActionController
# can do the following:
#
# class HelloController < ActionController::Metal
- # include ActionController::Rendering
+ # include AbstractController::Rendering
+ # include ActionView::Layouts
# append_view_path "#{Rails.root}/app/views"
#
# def index
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 75c4d3ef99..1abd8d3a33 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -96,7 +96,7 @@ module ActionController #:nodoc:
end
# Sends the given binary data to the browser. This method is similar to
- # <tt>render text: data</tt>, but also allows you to specify whether
+ # <tt>render plain: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
# the apparent file name, and other things.
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 158d552ec7..2eb7853aa6 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -11,11 +11,11 @@ module ActionController
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
# end
#
@@ -96,7 +96,7 @@ module ActionController
end
def user_name_and_password(request)
- decode_credentials(request).split(/:/, 2)
+ decode_credentials(request).split(':', 2)
end
def decode_credentials(request)
@@ -127,11 +127,11 @@ module ActionController
# before_action :authenticate, except: [:index]
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
#
# private
@@ -321,11 +321,11 @@ module ActionController
# before_action :authenticate, except: [ :index ]
#
# def index
- # render text: "Everyone can see me!"
+ # render plain: "Everyone can see me!"
# end
#
# def edit
- # render text: "I'm only accessible if you know the password"
+ # render plain: "I'm only accessible if you know the password"
# end
#
# private
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 33014b97ca..acf40b2e16 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -107,8 +107,11 @@ module ActionController
end
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
+ include MonitorMixin
+
def initialize(response)
- @error_callback = nil
+ @error_callback = lambda { true }
+ @cv = new_cond
super(response, SizedQueue.new(10))
end
@@ -122,14 +125,25 @@ module ActionController
end
def each
+ @response.sending!
while str = @buf.pop
yield str
end
+ @response.sent!
end
def close
- super
- @buf.push nil
+ synchronize do
+ super
+ @buf.push nil
+ @cv.broadcast
+ end
+ end
+
+ def await_close
+ synchronize do
+ @cv.wait_until { @closed }
+ end
end
def on_error(&block)
@@ -165,12 +179,20 @@ module ActionController
end
end
- def commit!
- headers.freeze
+ private
+
+ def before_committed
super
+ jar = request.cookie_jar
+ # The response can be committed multiple times
+ jar.write self unless committed?
end
- private
+ def before_sending
+ super
+ request.cookie_jar.commit!
+ headers.freeze
+ end
def build_buffer(response, body)
buf = Live::Buffer.new response
@@ -191,6 +213,7 @@ module ActionController
t1 = Thread.current
locals = t1.keys.map { |key| [key, t1[key]] }
+ error = nil
# This processes the action in a child thread. It lets us return the
# response code and headers back up the rack stack, and still process
# the body in parallel with sending data to the client
@@ -205,14 +228,18 @@ module ActionController
begin
super(name)
rescue => e
- begin
- @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
- @_response.stream.call_on_error
- rescue => exception
- log_error(exception)
- ensure
- log_error(e)
- @_response.stream.close
+ if @_response.committed?
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
+ else
+ error = e
end
ensure
@_response.commit!
@@ -220,6 +247,7 @@ module ActionController
}
@_response.await_commit
+ raise error if error
end
def log_error(exception)
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index d5e08b7034..1974bbf529 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -236,6 +236,18 @@ module ActionController #:nodoc:
# end
# end
#
+ # You can also set an array of variants:
+ #
+ # request.variant = [:tablet, :phone]
+ #
+ # which will work similarly to formats and MIME types negotiation. If there will be no
+ # :tablet variant declared, :phone variant will be picked:
+ #
+ # respond_to do |format|
+ # format.html.none
+ # format.html.phone # this gets rendered
+ # end
+ #
# Be sure to check the documentation of +respond_with+ and
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
def respond_to(*mimes, &block)
@@ -488,7 +500,7 @@ module ActionController #:nodoc:
response
else # `format.html{ |variant| variant.phone }` - variant block syntax
variant_collector = VariantCollector.new(@variant)
- response.call(variant_collector) #call format block with variants collector
+ response.call(variant_collector) # call format block with variants collector
variant_collector.variant
end
end
@@ -519,15 +531,15 @@ module ActionController #:nodoc:
end
def variant
- key = if @variant.nil?
- :none
- elsif @variants.has_key?(@variant)
- @variant
+ if @variant.nil?
+ @variants[:none] || @variants[:any]
+ elsif (@variants.keys & @variant).any?
+ @variant.each do |v|
+ return @variants[v] if @variants.key?(v)
+ end
else
- :any
+ @variants[:any]
end
-
- @variants[key]
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index c9f1d8dcb4..2ca8955741 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -231,7 +231,12 @@ module ActionController
# by the metal call stack.
def process_action(*args)
if _wrapper_enabled?
- wrapped_hash = _wrap_parameters request.request_parameters
+ if request.parameters[_wrapper_key].present?
+ wrapped_hash = _extract_parameters(request.parameters)
+ else
+ wrapped_hash = _wrap_parameters request.request_parameters
+ end
+
wrapped_keys = request.request_parameters.keys
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
@@ -259,14 +264,16 @@ module ActionController
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options.include
+ { _wrapper_key => _extract_parameters(parameters) }
+ end
+
+ def _extract_parameters(parameters)
+ if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
-
- { _wrapper_key => value }
end
# Checks if we should perform parameters wrapping.
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6c7b4652d4..0443b73953 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -42,8 +42,8 @@ module ActionController
nil
end
- # Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
+ # A Set containing renderer names that correspond to available renderer procs.
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 5c48b4ab98..93e7d6954c 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -2,6 +2,8 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
+ RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
@@ -27,14 +29,27 @@ module ActionController
end
def render_to_body(options = {})
- super || options[:text].presence || ' '
+ super || _render_in_priorities(options) || ' '
end
private
- def _process_format(format)
+ def _render_in_priorities(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ return options[format] if options.key?(format)
+ end
+
+ nil
+ end
+
+ def _process_format(format, options = {})
super
- self.content_type ||= format.to_s
+
+ if options[:plain]
+ self.content_type = Mime::TEXT
+ else
+ self.content_type ||= format.to_s
+ end
end
# Normalize arguments by catching blocks and setting them on :update.
@@ -46,12 +61,14 @@ module ActionController
# Normalize both text and status options.
def _normalize_options(options) #:nodoc:
- if options.key?(:text) && options[:text].respond_to?(:to_text)
- options[:text] = options[:text].to_text
+ _normalize_text(options)
+
+ if options[:html]
+ options[:html] = ERB::Util.html_escape(options[:html])
end
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
- options[:text] = " "
+ if options.delete(:nothing) || _any_render_format_is_nil?(options)
+ options[:body] = " "
end
if options[:status]
@@ -61,6 +78,18 @@ module ActionController
super
end
+ def _normalize_text(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ if options.key?(format) && options[format].respond_to?(:to_text)
+ options[format] = options[format].to_text
+ end
+ end
+ end
+
+ def _any_render_format_is_nil?(options)
+ RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
+ end
+
# Process controller specific options, as status, content-type and location.
def _process_options(options) #:nodoc:
status, content_type, location = options.values_at(:status, :content_type, :location)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index c88074d4c6..e3b1f5ae7c 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -68,6 +68,10 @@ module ActionController #:nodoc:
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
+ # Controls whether a CSRF failure logs a warning. On by default.
+ config_accessor :log_warning_on_csrf_failure
+ self.log_warning_on_csrf_failure = true
+
helper_method :form_authenticity_token
helper_method :protect_against_forgery?
end
@@ -193,7 +197,9 @@ module ActionController #:nodoc:
mark_for_same_origin_verification!
if !verified_request?
- logger.warn "Can't verify CSRF token authenticity" if logger
+ if logger && log_warning_on_csrf_failure
+ logger.warn "Can't verify CSRF token authenticity"
+ end
handle_unverified_request
end
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index e24b56fa91..5096558c67 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -22,7 +22,7 @@ module ActionController #:nodoc:
#
# 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
#
- # === Builtin HTTP verb semantics
+ # === Built-in HTTP verb semantics
#
# The default \Rails responder holds semantics for each HTTP verb. Depending on the
# content type, verb and the resource status, it will behave differently.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 48a916f2b1..d86d49c9dc 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -32,7 +32,7 @@ module ActionController
def initialize(params) # :nodoc:
@params = params
- super("found unpermitted parameters: #{params.join(", ")}")
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
end
end
@@ -502,7 +502,7 @@ module ActionController
# end
# end
#
- # In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you
+ # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
# will need to specify which nested attributes should be whitelisted.
#
# class Person
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index 0377b8c4cf..dd8da4b5dc 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -17,7 +17,6 @@ module ActionController
def recycle!
@_url_options = nil
- self.response_body = nil
self.formats = nil
self.params = nil
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 754249cbc8..37d4a96ee1 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.symbolized_path_parameters
).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
+ 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'])
+ (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
@_url_options.dup.tap do |options|
if original_script_name
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 5ed3d2ebc1..caaebc537a 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -17,8 +17,9 @@ module ActionController
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
@_files = Hash.new(0)
+ @_subscribers = []
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -28,7 +29,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -41,7 +42,7 @@ module ActionController
@_templates[path] += 1
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
next if payload[:virtual_path] # files don't have virtual path
path = payload[:identifier]
@@ -53,8 +54,9 @@ module ActionController
end
def teardown_subscriptions
- ActiveSupport::Notifications.unsubscribe("render_template.action_view")
- ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
+ @_subscribers.each do |subscriber|
+ ActiveSupport::Notifications.unsubscribe(subscriber)
+ end
end
def process(*args)
@@ -213,6 +215,9 @@ module ActionController
# Clear the combined params hash in case it was already referenced.
@env.delete("action_dispatch.request.parameters")
+ # Clear the filter cache variables so they're not stale
+ @filtered_parameters = @filtered_env = @filtered_path = nil
+
params = self.request_parameters.dup
%w(controller action only_path).each do |k|
params.delete(k)
@@ -255,6 +260,29 @@ module ActionController
end
end
+ class LiveTestResponse < Live::Response
+ def recycle!
+ @body = nil
+ initialize
+ end
+
+ def body
+ @body ||= super
+ end
+
+ # Was the response successful?
+ alias_method :success?, :successful?
+
+ # Was the URL not found?
+ alias_method :missing?, :not_found?
+
+ # Were we redirected?
+ alias_method :redirect?, :redirection?
+
+ # Was there a server-side error?
+ alias_method :error?, :server_error?
+ end
+
# Methods #destroy and #load! are overridden to avoid calling methods on the
# @store object, which does not exist for the TestSession class.
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
@@ -460,8 +488,8 @@ module ActionController
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
- # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with
- # +post+, +patch+, +put+, +delete+, +head+, and +options+.
+ # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +post+, +patch+, +put+, +delete+, and +head+.
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
@@ -565,10 +593,13 @@ module ActionController
name = @request.parameters[:action]
+ @controller.recycle!
@controller.process(name)
if cookies = @request.env['action_dispatch.cookies']
- cookies.write(@response)
+ unless @response.committed?
+ cookies.write(@response)
+ end
end
@response.prepare!
@@ -579,13 +610,14 @@ module ActionController
end
def setup_controller_request_and_response
- @request = build_request
- @response = build_response
- @response.request = @request
-
@controller = nil unless defined? @controller
+ response_klass = TestResponse
+
if klass = self.class.controller_class
+ if klass < ActionController::Live
+ response_klass = LiveTestResponse
+ end
unless @controller
begin
@controller = klass.new
@@ -595,6 +627,10 @@ module ActionController
end
end
+ @request = build_request
+ @response = build_response response_klass
+ @response.request = @request
+
if @controller
@controller.request = @request
@controller.params = {}
@@ -605,8 +641,8 @@ module ActionController
TestRequest.new
end
- def build_response
- TestResponse.new
+ def build_response(klass)
+ klass.new
end
included do
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 920e651b08..11b5e6be33 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -52,7 +52,6 @@ module ActionDispatch
autoload :DebugExceptions
autoload :ExceptionWrapper
autoload :Flash
- autoload :Head
autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
@@ -74,18 +73,16 @@ module ActionDispatch
autoload :MimeNegotiation
autoload :Parameters
autoload :ParameterFilter
- autoload :FilterParameters
- autoload :FilterRedirect
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
autoload :URL
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 289e204ac8..2b851cc28d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -6,8 +6,8 @@ module ActionDispatch
module Http
# Allows you to specify sensitive parameters which will be replaced from
# the request log by looking in the query string of the request and all
- # subhashes of the params hash to filter. If a block is given, each key and
- # value of the params hash and all subhashes is passed to it, the value
+ # sub-hashes of the params hash to filter. If a block is given, each key and
+ # value of the params hash and all sub-hashes is passed to it, the value
# or key can be replaced using String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
index 900ce1c646..cd603649c3 100644
--- a/actionpack/lib/action_dispatch/http/filter_redirect.rb
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -5,7 +5,8 @@ module ActionDispatch
FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location
- if !location_filter.empty? && location_filter_match?
+ filters = location_filter
+ if !filters.empty? && location_filter_match?(filters)
FILTERED
else
location
@@ -15,15 +16,15 @@ module ActionDispatch
private
def location_filter
- if request.present?
+ if request
request.env['action_dispatch.redirect_filter'] || []
else
[]
end
end
- def location_filter_match?
- location_filter.any? do |filter|
+ def location_filter_match?(filters)
+ filters.any? do |filter|
if String === filter
location.include?(filter)
elsif Regexp === filter
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index c33ba201e1..0b2b60d2e4 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -68,10 +68,12 @@ module ActionDispatch
# Sets the \variant for template.
def variant=(variant)
- if variant.is_a? Symbol
+ if variant.is_a?(Symbol)
+ @variant = [variant]
+ elsif 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, not a #{variant.class}. " \
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
"For security reasons, never directly set the variant to a user-provided value, " \
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
@@ -127,7 +129,7 @@ module ActionDispatch
end
end
- order.include?(Mime::ALL) ? formats.first : nil
+ order.include?(Mime::ALL) ? format : nil
end
protected
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 3d2dd2d632..9450be838c 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -174,7 +174,7 @@ module Mime
end
def parse(accept_header)
- if accept_header !~ /,/
+ if !accept_header.include?(',')
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 1318c62fbe..daa06e96e6 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -152,6 +152,13 @@ module ActionDispatch
Http::Headers.new(@env)
end
+ # Returns a +String+ with the last requested path including their params.
+ #
+ # # get '/foo'
+ # request.original_fullpath # => '/foo'
+ #
+ # # get '/foo?bar'
+ # request.original_fullpath # => '/foo?bar'
def original_fullpath
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 7b2655b2d8..3d27ff2b24 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'action_dispatch/http/filter_redirect'
require 'monitor'
module ActionDispatch # :nodoc:
@@ -90,7 +91,10 @@ module ActionDispatch # :nodoc:
end
def each(&block)
- @buf.each(&block)
+ @response.sending!
+ x = @buf.each(&block)
+ @response.sent!
+ x
end
def close
@@ -117,6 +121,8 @@ module ActionDispatch # :nodoc:
@blank = false
@cv = new_cond
@committed = false
+ @sending = false
+ @sent = false
@content_type = nil
@charset = nil
@@ -137,17 +143,37 @@ module ActionDispatch # :nodoc:
end
end
+ def await_sent
+ synchronize { @cv.wait_until { @sent } }
+ end
+
def commit!
synchronize do
+ before_committed
@committed = true
@cv.broadcast
end
end
- def committed?
- @committed
+ def sending!
+ synchronize do
+ before_sending
+ @sending = true
+ @cv.broadcast
+ end
end
+ def sent!
+ synchronize do
+ @sent = true
+ @cv.broadcast
+ end
+ end
+
+ def sending?; synchronize { @sending }; end
+ def committed?; synchronize { @committed }; end
+ def sent?; synchronize { @sent }; end
+
# Sets the HTTP status code.
def status=(status)
@status = Rack::Utils.status_code(status)
@@ -272,6 +298,12 @@ module ActionDispatch # :nodoc:
private
+ def before_committed
+ end
+
+ def before_sending
+ end
+
def merge_default_headers(original, default)
return original unless default.respond_to?(:merge)
@@ -312,7 +344,7 @@ module ActionDispatch # :nodoc:
header.delete CONTENT_TYPE
[status, header, []]
else
- [status, header, self]
+ [status, header, Rack::BodyProxy.new(self){}]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index a8d2dc3950..45bf751d09 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -18,6 +18,7 @@ module ActionDispatch
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
attr_accessor :tempfile
+ alias :to_io :tempfile
# A string with the headers of the multipart request.
attr_accessor :headers
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 4410c1b5d5..57f0963731 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -121,9 +121,9 @@ module ActionDispatch
def possibles(cache, options, depth = 0)
cache.fetch(:___routes) { [] } + options.find_all { |pair|
cache.key?(pair)
- }.map { |pair|
+ }.flat_map { |pair|
possibles(cache[pair], options, depth + 1)
- }.flatten(1)
+ }
end
# Returns +true+ if no missing keys are present, otherwise +false+.
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
index 7d2791714b..450588cda6 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -27,7 +27,7 @@ module ActionDispatch
marked[s] = true # mark s
s.group_by { |state| symbol(state) }.each do |sym, ps|
- u = ps.map { |l| followpos(l) }.flatten
+ u = ps.flat_map { |l| followpos(l) }
next if u.empty?
if u.uniq == [DUMMY]
@@ -90,7 +90,7 @@ module ActionDispatch
firstpos(node.left)
end
when Nodes::Or
- node.children.map { |c| firstpos(c) }.flatten.uniq
+ node.children.flat_map { |c| firstpos(c) }.uniq
when Nodes::Unary
firstpos(node.left)
when Nodes::Terminal
@@ -105,7 +105,7 @@ module ActionDispatch
when Nodes::Star
firstpos(node.left)
when Nodes::Or
- node.children.map { |c| lastpos(c) }.flatten.uniq
+ node.children.flat_map { |c| lastpos(c) }.uniq
when Nodes::Cat
if nullable?(node.right)
lastpos(node.left) | lastpos(node.right)
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
index 58ad803841..94b0a24344 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -19,6 +19,14 @@ module ActionDispatch
end
def simulate(string)
+ ms = memos(string) { return }
+ MatchData.new(ms)
+ end
+
+ alias :=~ :simulate
+ alias :match :simulate
+
+ def memos(string)
input = StringScanner.new(string)
state = [0]
while sym = input.scan(%r([/.?]|[^/.?]+))
@@ -29,15 +37,10 @@ module ActionDispatch
tt.accepting? s
}
- return if acceptance_states.empty?
+ return yield if acceptance_states.empty?
- memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
-
- MatchData.new(memos)
+ acceptance_states.flat_map { |x| tt.memo(x) }.compact
end
-
- alias :=~ :simulate
- alias :match :simulate
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 5a79059ed6..990d2127ee 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -40,7 +40,19 @@ module ActionDispatch
end
def move(t, a)
- move_string(t, a).concat(move_regexp(t, a))
+ return [] if t.empty?
+
+ regexps = []
+
+ t.map { |s|
+ if states = @regexp_states[s]
+ regexps.concat states.map { |re, v| re === a ? v : nil }
+ end
+
+ if states = @string_states[s]
+ states[a]
+ end
+ }.compact.concat regexps
end
def as_json(options = nil)
@@ -114,17 +126,17 @@ module ActionDispatch
end
def states
- ss = @string_states.keys + @string_states.values.map(&:values).flatten
- rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
+ ss = @string_states.keys + @string_states.values.flat_map(&:values)
+ rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
(ss + rs).uniq
end
def transitions
- @string_states.map { |from, hash|
+ @string_states.flat_map { |from, hash|
hash.map { |s, to| [from, s, to] }
- }.flatten(1) + @regexp_states.map { |from, hash|
+ } + @regexp_states.flat_map { |from, hash|
hash.map { |s, to| [from, s, to] }
- }.flatten(1)
+ }
end
private
@@ -139,26 +151,6 @@ module ActionDispatch
raise ArgumentError, 'unknown symbol: %s' % sym.class
end
end
-
- def move_regexp(t, a)
- return [] if t.empty?
-
- t.map { |s|
- if states = @regexp_states[s]
- states.map { |re, v| re === a ? v : nil }
- end
- }.flatten.compact.uniq
- end
-
- def move_string(t, a)
- return [] if t.empty?
-
- t.map do |s|
- if states = @string_states[s]
- states[a]
- end
- end.compact
- end
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
index 5c33a872e5..47bf76bdbf 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -16,9 +16,9 @@ module ActionDispatch
# end
# " #{n.object_id} [label=\"#{label}\", shape=box];"
#}
- #memo_edges = memos.map { |k, memos|
+ #memo_edges = memos.flat_map { |k, memos|
# (memos || []).map { |v| " #{k} -> #{v.object_id};" }
- #}.flatten.uniq
+ #}.uniq
<<-eodot
digraph nfa {
diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
index 5b40da6569..b23270db3c 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
@@ -34,7 +34,7 @@ module ActionDispatch
return if acceptance_states.empty?
- memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
+ memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact
MatchData.new(memos)
end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index a3017aeea1..66e414213a 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -42,7 +42,7 @@ module ActionDispatch
end
def states
- (@table.keys + @table.values.map(&:keys).flatten).uniq
+ (@table.keys + @table.values.flat_map(&:keys)).uniq
end
# Returns a generalized transition graph with reduced states. The states
@@ -93,7 +93,7 @@ module ActionDispatch
# 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)
- Array(t).map { |s| inverted[s][a] }.flatten.uniq
+ Array(t).flat_map { |s| inverted[s][a] }.uniq
end
# Returns set of NFA states to which there is a transition on ast symbol
@@ -107,7 +107,7 @@ module ActionDispatch
end
def alphabet
- inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
+ inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s }
end
# Returns a set of NFA states reachable from some NFA state +s+ in set
@@ -131,9 +131,9 @@ module ActionDispatch
end
def transitions
- @table.map { |to, hash|
+ @table.flat_map { |to, hash|
hash.map { |from, sym| [from, sym, to] }
- }.flatten(1)
+ }
end
private
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index d37aa1fbe5..fb155e516f 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -53,9 +53,9 @@ module ActionDispatch
end
def optional_names
- @optional_names ||= spec.grep(Nodes::Group).map { |group|
+ @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
group.grep(Nodes::Symbol)
- }.flatten.map { |n| n.name }.uniq
+ }.map { |n| n.name }.uniq
end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index c8eb0f6f2d..2b399d3ee3 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -101,6 +101,10 @@ module ActionDispatch
end
end
+ def glob?
+ !path.spec.grep(Nodes::Star).empty?
+ end
+
def dispatcher?
@dispatcher
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index da32f1bfe7..36561c71a1 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -54,7 +54,7 @@ module ActionDispatch
end
def call(env)
- env['PATH_INFO'] = normalize_path(env['PATH_INFO'])
+ env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
find_routes(env).each do |match, parameters, route|
script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
@@ -103,12 +103,6 @@ module ActionDispatch
private
- def normalize_path(path)
- path = "/#{path}"
- path.squeeze!('/')
- path
- end
-
def partitioned_routes
routes.partitioned_routes
end
@@ -127,8 +121,7 @@ module ActionDispatch
def filter_routes(path)
return [] unless ast
- data = simulator.match(path)
- data ? data.memos : []
+ simulator.memos(path) { [] }
end
def find_routes env
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index d1a004af50..ac4ecb1e65 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -1,5 +1,3 @@
-require 'uri'
-
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
@@ -25,31 +23,67 @@ module ActionDispatch
# URI path and fragment escaping
# http://tools.ietf.org/html/rfc3986
- module UriEscape # :nodoc:
- # Symbol captures can generate multiple path segments, so include /.
- reserved_segment = '/'
- reserved_fragment = '/?'
- reserved_pchar = ':@&=+$,;%'
-
- safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
- safe_segment = "#{safe_pchar}#{reserved_segment}"
- safe_fragment = "#{safe_pchar}#{reserved_fragment}"
- UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
- UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
+ class UriEncoder # :nodoc:
+ ENCODE = "%%%02X".freeze
+ ENCODING = Encoding::US_ASCII
+ EMPTY = "".force_encoding(ENCODING).freeze
+ DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(ENCODING) }
+
+ ALPHA = "a-zA-Z".freeze
+ DIGIT = "0-9".freeze
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
+
+ ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
+
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
+ SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
+ PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
+
+ def escape_fragment(fragment)
+ escape(fragment, FRAGMENT)
+ end
+
+ def escape_path(path)
+ escape(path, PATH)
+ end
+
+ def escape_segment(segment)
+ escape(segment, SEGMENT)
+ end
+
+ def unescape_uri(uri)
+ uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(uri.encoding)
+ end
+
+ protected
+ def escape(component, pattern)
+ component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(ENCODING)
+ end
+
+ def percent_encode(unsafe)
+ safe = EMPTY.dup
+ unsafe.each_byte { |b| safe << DEC2HEX[b] }
+ safe
+ end
end
- Parser = URI::Parser.new
+ ENCODER = UriEncoder.new
def self.escape_path(path)
- Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
+ ENCODER.escape_path(path.to_s)
+ end
+
+ def self.escape_segment(segment)
+ ENCODER.escape_segment(segment.to_s)
end
def self.escape_fragment(fragment)
- Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
+ ENCODER.escape_fragment(fragment.to_s)
end
def self.unescape_uri(uri)
- Parser.unescape(uri)
+ ENCODER.unescape_uri(uri)
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index daade5bb74..d9f634623d 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -114,19 +114,26 @@ module ActionDispatch
end
private
+ def escape_path(value)
+ Router::Utils.escape_path(value)
+ end
+
+ def escape_segment(value)
+ Router::Utils.escape_segment(value)
+ end
def visit(node, optional = false)
case node.type
when :LITERAL, :SLASH, :DOT
node.left
when :STAR
- visit(node.left)
+ visit_STAR(node.left)
when :GROUP
visit(node.left, true)
when :CAT
visit_CAT(node, optional)
when :SYMBOL
- visit_SYMBOL(node)
+ visit_SYMBOL(node, node.to_sym)
end
end
@@ -141,9 +148,15 @@ module ActionDispatch
end
end
- def visit_SYMBOL(node)
+ def visit_STAR(node)
if value = options[node.to_sym]
- Router::Utils.escape_path(value)
+ escape_path(value)
+ end
+ end
+
+ def visit_SYMBOL(node, name)
+ if value = options[name]
+ name == :controller ? escape_path(value) : escape_segment(value)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index fe110d7938..22b16b628d 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -23,15 +23,15 @@ module ActionDispatch
# # This cookie will be deleted when the user's browser is closed.
# cookies[:user_name] = "david"
#
- # # Assign an array of values to a cookie.
- # cookies[:lat_lon] = [47.68, -122.37]
+ # # Cookie values are String based. Other data types need to be serialized.
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
+ # # The cookie is signed by your app's `secrets.secret_key_base` value.
+ # # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -42,10 +42,10 @@ module ActionDispatch
#
# Examples of reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
- # cookies.signed[:login] # => "XJ-122"
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
@@ -63,7 +63,7 @@ module ActionDispatch
#
# The option symbols for setting cookies are:
#
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
+ # * <tt>:value</tt> - The cookie's value.
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
# of the application.
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
@@ -74,7 +74,7 @@ module ActionDispatch
#
# domain: nil # Does not sets cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
- # domain and subdomains.
+ # # domain and subdomains.
#
# * <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.
@@ -89,6 +89,7 @@ module ActionDispatch
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -175,12 +176,12 @@ module ActionDispatch
module VerifyAndUpgradeLegacySignedMessage
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
- @legacy_verifier.verify(signed_message).tap do |value|
- self[name] = value
+ deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
+ self[name] = { value: value }
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
@@ -210,7 +211,8 @@ module ActionDispatch
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
+ serializer: env[COOKIES_SERIALIZER]
}
end
@@ -235,6 +237,15 @@ module ActionDispatch
@secure = secure
@options = options
@cookies = {}
+ @committed = false
+ end
+
+ def committed?; @committed; end
+
+ def commit!
+ @committed = true
+ @set_cookies.freeze
+ @delete_cookies.freeze
end
def each(&block)
@@ -334,8 +345,8 @@ module ActionDispatch
end
def recycle! #:nodoc:
- @set_cookies.clear
- @delete_cookies.clear
+ @set_cookies = {}
+ @delete_cookies = {}
end
mattr_accessor :always_write_cookie
@@ -372,28 +383,89 @@ module ActionDispatch
end
end
+ class JsonSerializer
+ def self.load(value)
+ JSON.parse(value, quirks_mode: true)
+ end
+
+ def self.dump(value)
+ JSON.generate(value, quirks_mode: true)
+ end
+ end
+
+ # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
+ # allows us to handle the (de)serialization step within the cookie jar,
+ # which gives us the opportunity to detect and migrate legacy cookies.
+ class NullSerializer
+ def self.load(value)
+ value
+ end
+
+ def self.dump(value)
+ value
+ end
+ end
+
+ module SerializedCookieJars
+ MARSHAL_SIGNATURE = "\x04\x08".freeze
+
+ protected
+ def needs_migration?(value)
+ @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
+ end
+
+ def serialize(name, value)
+ serializer.dump(value)
+ end
+
+ def deserialize(name, value)
+ if value
+ if needs_migration?(value)
+ Marshal.load(value).tap do |v|
+ self[name] = { value: v }
+ end
+ else
+ serializer.load(value)
+ end
+ end
+ end
+
+ def serializer
+ serializer = @options[:serializer] || :marshal
+ case serializer
+ when :marshal
+ Marshal
+ when :json, :hybrid
+ JsonSerializer
+ else
+ serializer
+ end
+ end
+ end
+
class SignedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
end
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message)
+ deserialize name, verify(signed_message)
end
end
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
- options[:value] = @verifier.generate(options[:value])
+ options[:value] = @verifier.generate(serialize(name, options[:value]))
else
- options = { :value => @verifier.generate(options) }
+ options = { :value => @verifier.generate(serialize(name, options)) }
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@@ -417,13 +489,14 @@ module ActionDispatch
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message)
end
end
end
class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
@@ -435,12 +508,12 @@ module ActionDispatch
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
end
def [](name)
if encrypted_message = @parent_jar[name]
- decrypt_and_verify(encrypted_message)
+ deserialize name, decrypt_and_verify(encrypted_message)
end
end
@@ -450,7 +523,8 @@ module ActionDispatch
else
options = { :value => options }
end
- options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[name] = options
@@ -473,7 +547,7 @@ module ActionDispatch
def [](name)
if encrypted_or_signed_message = @parent_jar[name]
- decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
end
end
end
@@ -486,9 +560,11 @@ module ActionDispatch
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
- cookie_jar.write(headers)
- if headers[HTTP_HEADER].respond_to?(:join)
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
+ unless cookie_jar.committed?
+ cookie_jar.write(headers)
+ if headers[HTTP_HEADER].respond_to?(:join)
+ headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 377f05c982..2326bb043a 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -32,6 +32,8 @@ module ActionDispatch
def initialize(env, exception)
@env = env
@exception = original_exception(exception)
+
+ expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
end
def rescue_template
@@ -104,5 +106,11 @@ module ActionDispatch
end
end
end
+
+ def expand_backtrace
+ @exception.backtrace.unshift(
+ @exception.to_s.split("\n")
+ ).flatten!
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 89003e7a5e..4821d2a899 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/keys'
+
module ActionDispatch
class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -50,13 +52,14 @@ module ActionDispatch
end
def []=(k, v)
+ k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
- @flash[k]
+ @flash[k.to_s]
end
# Convenience accessor for <tt>flash.now[:alert]=</tt>.
@@ -92,8 +95,8 @@ module ActionDispatch
end
def initialize(flashes = {}, discard = []) #:nodoc:
- @discard = Set.new(discard)
- @flashes = flashes
+ @discard = Set.new(stringify_array(discard))
+ @flashes = flashes.stringify_keys
@now = nil
end
@@ -106,17 +109,18 @@ module ActionDispatch
end
def []=(k, v)
+ k = k.to_s
@discard.delete k
@flashes[k] = v
end
def [](k)
- @flashes[k]
+ @flashes[k.to_s]
end
def update(h) #:nodoc:
- @discard.subtract h.keys
- @flashes.update h
+ @discard.subtract stringify_array(h.keys)
+ @flashes.update h.stringify_keys
self
end
@@ -129,6 +133,7 @@ module ActionDispatch
end
def delete(key)
+ key = key.to_s
@discard.delete key
@flashes.delete key
self
@@ -155,7 +160,7 @@ module ActionDispatch
def replace(h) #:nodoc:
@discard.clear
- @flashes.replace h
+ @flashes.replace h.stringify_keys
self
end
@@ -186,6 +191,7 @@ module ActionDispatch
# flash.keep # keeps the entire flash
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
def keep(k = nil)
+ k = k.to_s if k
@discard.subtract Array(k || keys)
k ? self[k] : self
end
@@ -195,6 +201,7 @@ module ActionDispatch
# flash.discard # discard the entire flash at the end of the current action
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
def discard(k = nil)
+ k = k.to_s if k
@discard.merge Array(k || keys)
k ? self[k] : self
end
@@ -231,6 +238,12 @@ module ActionDispatch
def now_is_loaded?
@now
end
+
+ def stringify_array(array)
+ array.map do |item|
+ item.kind_of?(Symbol) ? item.to_s : item
+ end
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 2f6968eb2e..15b5a48535 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/reporting'
+
module ActionDispatch
# ActionDispatch::Reloader provides prepare and cleanup callbacks,
# intended to assist with code reloading during development.
@@ -25,19 +27,26 @@ module ActionDispatch
#
class Reloader
include ActiveSupport::Callbacks
+ include ActiveSupport::Deprecation::Reporting
- define_callbacks :prepare, :scope => :name
- define_callbacks :cleanup, :scope => :name
+ define_callbacks :prepare
+ define_callbacks :cleanup
# Add a prepare callback. Prepare callbacks are run before each request, prior
# to ActionDispatch::Callback's before callbacks.
def self.to_prepare(*args, &block)
+ unless block_given?
+ warn "to_prepare without a block is deprecated. Please use a block"
+ end
set_callback(:prepare, *args, &block)
end
# Add a cleanup callback. Cleanup callbacks are run after each request is
# complete (after #close is called on the response body).
def self.to_cleanup(*args, &block)
+ unless block_given?
+ warn "to_cleanup without a block is deprecated. Please use a block"
+ end
set_callback(:cleanup, *args, &block)
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 57bc6d5cd0..c1df518b14 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -47,12 +47,12 @@ module ActionDispatch
# clients (like WAP devices), or behind proxies that set headers in an
# incorrect or confusing way (like AWS ELB).
#
- # The +custom_trusted+ argument can take a regex, which will be used
+ # The +custom_proxies+ argument can take a regex, which will be used
# instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
# to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
# middle (or at the beginning) of the X-Forwarded-For list, with your proxy
# servers after it. If your proxies aren't removed, pass them in via the
- # +custom_trusted+ parameter. That way, the middleware will ignore those
+ # +custom_proxies+ parameter. That way, the middleware will ignore those
# IP addresses, and return the one that you want.
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 1ebc189c28..0864e7ef2a 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -29,7 +29,7 @@ module ActionDispatch
#
# Configure your session store in config/initializers/session_store.rb:
#
- # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
# Configure your secret key in config/secrets.yml:
#
@@ -51,7 +51,7 @@ module ActionDispatch
# decode signed cookies generated by your app in external applications or
# Javascript before upgrading.
#
- # Note that changing digest or secret invalidates all existing sessions!
+ # Note that changing the secret key will invalidate all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 999c022535..0c7caef25d 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -32,11 +32,14 @@ module ActionDispatch
private
def redirect_to_https(request)
- url = URI(request.url)
- url.scheme = "https"
- url.host = @host if @host
- url.port = @port if @port
- headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s }
+ host = @host || request.host
+ port = @port || request.port
+
+ location = "https://#{host}"
+ location << ":#{port}" if port != 80
+ location << request.fullpath
+
+ headers = { 'Content-Type' => 'text/html', 'Location' => location }
[301, headers, []]
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
index f154021ae6..f154021ae6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
new file mode 100644
index 0000000000..603de54b8b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
@@ -0,0 +1,9 @@
+<%= @exception.class.to_s %><%
+ if @request.parameters['controller']
+%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+<% end %>
+
+<%= @exception.message %>
+<%= render template: "rescues/_source" %>
+<%= render template: "rescues/_trace" %>
+<%= render template: "rescues/_request_and_response" %>
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 323873ba4b..cce0d75af4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -4,21 +4,41 @@
border-collapse: collapse;
}
- #route_table td {
- padding: 0 30px;
+ #route_table thead tr {
+ border-bottom: 2px solid #ddd;
+ }
+
+ #route_table thead tr.bottom {
+ border-bottom: none;
}
- #route_table tr.bottom th {
- padding-bottom: 10px;
+ #route_table thead tr.bottom th {
+ padding: 10px 0;
line-height: 15px;
}
- #route_table .matched_paths {
+ #route_table tbody tr {
+ border-bottom: 1px solid #ddd;
+ }
+
+ #route_table tbody tr:nth-child(odd) {
+ background: #f2f2f2;
+ }
+
+ #route_table tbody.exact_matches,
+ #route_table tbody.fuzzy_matches {
background-color: LightGoldenRodYellow;
+ border-bottom: solid 2px SlateGrey;
}
- #route_table .matched_paths {
- border-bottom: solid 3px SlateGrey;
+ #route_table tbody.exact_matches tr,
+ #route_table tbody.fuzzy_matches tr {
+ background: none;
+ border-bottom: none;
+ }
+
+ #route_table td {
+ padding: 4px 30px;
}
#path_search {
@@ -45,13 +65,15 @@
<th><%# HTTP Verb %>
</th>
<th><%# Path %>
- <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %>
+ <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %>
</th>
<th><%# Controller#action %>
</th>
</tr>
</thead>
- <tbody class='matched_paths' id='matched_paths'>
+ <tbody class='exact_matches' id='exact_matches'>
+ </tbody>
+ <tbody class='fuzzy_matches' id='fuzzy_matches'>
</tbody>
<tbody>
<%= yield %>
@@ -59,6 +81,7 @@
</table>
<script type='text/javascript'>
+ // Iterates each element through a function
function each(elems, func) {
if (!elems instanceof Array) { elems = [elems]; }
for (var i = 0, len = elems.length; i < len; i++) {
@@ -66,77 +89,110 @@
}
}
- function setValOn(elems, val) {
- each(elems, function(elem) {
- elem.innerHTML = val;
- });
+ // Sets innerHTML for an element
+ function setContent(elem, text) {
+ elem.innerHTML = text;
}
- function onClick(elems, func) {
- each(elems, function(elem) {
- elem.onclick = func;
- });
- }
+ // Enables path search functionality
+ function setupMatchPaths() {
+ // Check if the user input (sanitized as a path) matches the regexp data attribute
+ function checkExactMatch(section, elem, value) {
+ var string = sanitizePath(value),
+ regexp = elem.getAttribute("data-regexp");
- // Enables functionality to toggle between `_path` and `_url` helper suffixes
- function setupRouteToggleHelperLinks() {
- var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
- onClick(toggleLinks, function(){
- var helperTxt = this.getAttribute("data-route-helper"),
- helperElems = document.querySelectorAll('[data-route-name] span.helper');
- setValOn(helperElems, helperTxt);
- });
- }
+ showMatch(string, regexp, section, elem);
+ }
- // takes an array of elements with a data-regexp attribute and
- // passes their parent <tr> into the callback function
- // if the regexp matches a given path
- function eachElemsForPath(elems, path, func) {
- each(elems, function(e){
- var reg = e.getAttribute("data-regexp");
- if (path.match(RegExp(reg))) {
- func(e.parentNode.cloneNode(true));
- }
- })
- }
+ // Check if the route path data attribute contains the user input
+ function checkFuzzyMatch(section, elem, value) {
+ var string = elem.getAttribute("data-route-path"),
+ regexp = value;
- // Ensure path always starts with a slash "/" and remove params or fragments
- function sanitizePath(path) {
- var path = path.charAt(0) == '/' ? path : "/" + path;
- return path.replace(/\#.*|\?.*/, '');
- }
+ showMatch(string, regexp, section, elem);
+ }
- // Enables path search functionality
- function setupMatchPaths() {
- var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
- pathElem = document.querySelector('#path_search'),
- selectedSection = document.querySelector('#matched_paths'),
- noMatchText = '<tr><th colspan="4">None</th></tr>';
+ // Display the parent <tr> element in the appropriate section when there's a match
+ function showMatch(string, regexp, section, elem) {
+ if(string.match(RegExp(regexp))) {
+ section.appendChild(elem.parentNode.cloneNode(true));
+ }
+ }
+
+ // Check if there are any matched results in a section
+ function checkNoMatch(section, defaultText, noMatchText) {
+ if (section.innerHTML === defaultText) {
+ setContent(section, defaultText + noMatchText);
+ }
+ }
+ // Ensure path always starts with a slash "/" and remove params or fragments
+ function sanitizePath(path) {
+ var path = path.charAt(0) == '/' ? path : "/" + path;
+ return path.replace(/\#.*|\?.*/, '');
+ }
- // Remove matches if no path is present
- pathElem.onblur = function(e) {
- if (pathElem.value === "") selectedSection.innerHTML = "";
+ var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
+ searchElem = document.querySelector('#search'),
+ exactMatches = document.querySelector('#exact_matches'),
+ fuzzyMatches = document.querySelector('#fuzzy_matches');
+
+ // Remove matches when no search value is present
+ searchElem.onblur = function(e) {
+ if (searchElem.value === "") {
+ setContent(exactMatches, "");
+ setContent(fuzzyMatches, "");
+ }
}
// On key press perform a search for matching paths
- pathElem.onkeyup = function(e){
- var path = sanitizePath(pathElem.value),
- defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>';
+ searchElem.onkeyup = function(e){
+ var userInput = searchElem.value,
+ defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>',
+ defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>',
+ noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
+ noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
// Clear out results section
- selectedSection.innerHTML= defaultText;
+ setContent(exactMatches, defaultExactMatch);
+ setContent(fuzzyMatches, defaultFuzzyMatch);
+
+ // Display exact matches and fuzzy matches
+ each(regexpElems, function(elem) {
+ checkExactMatch(exactMatches, elem, userInput);
+ checkFuzzyMatch(fuzzyMatches, elem, userInput);
+ })
+
+ // Display 'No Matches' message when no matches are found
+ checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
+ checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
+ }
+ }
- // Display matches if they exist
- eachElemsForPath(regexpElems, path, function(e){
- selectedSection.appendChild(e);
+ // Enables functionality to toggle between `_path` and `_url` helper suffixes
+ function setupRouteToggleHelperLinks() {
+
+ // Sets content for each element
+ function setValOn(elems, val) {
+ each(elems, function(elem) {
+ setContent(elem, val);
});
+ }
- // If no match present, tell the user
- if (selectedSection.innerHTML === defaultText) {
- selectedSection.innerHTML = selectedSection.innerHTML + noMatchText;
- }
+ // Sets onClick event for each element
+ function onClick(elems, func) {
+ each(elems, function(elem) {
+ elem.onclick = func;
+ });
}
+
+ var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
+ onClick(toggleLinks, function(){
+ var helperTxt = this.getAttribute("data-route-helper"),
+ helperElems = document.querySelectorAll('[data-route-name] span.helper');
+
+ setValOn(helperElems, helperTxt);
+ });
}
setupMatchPaths();
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index a6dca9741c..9d4f1aa3c5 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -7,18 +7,23 @@ module ActionDispatch
class << self
# Remove nils from the params hash
- def deep_munge(hash)
+ def deep_munge(hash, keys = [])
return hash unless perform_deep_munge
hash.each do |k, v|
+ keys << k
case v
when Array
- v.grep(Hash) { |x| deep_munge(x) }
+ v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
- hash[k] = nil if v.empty?
+ if v.empty?
+ hash[k] = nil
+ ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
+ end
when Hash
- deep_munge(v)
+ deep_munge(v, keys)
end
+ keys.pop
end
hash
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index a9ac2bce1d..9cd884daa3 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -11,7 +11,7 @@ module ActionDispatch
# Think of creating routes as drawing a map for your requests. The map tells
# them where to go based on some predefined pattern:
#
- # AppName::Application.routes.draw do
+ # Rails.application.routes.draw do
# Pattern 1 tells some request to go to one place
# Pattern 2 tell them to go to another
# ...
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index f612e91aef..71a0c5e826 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -194,9 +194,9 @@ module ActionDispatch
end
def widths(routes)
- [routes.map { |r| r[:name].length }.max,
- routes.map { |r| r[:verb].length }.max,
- routes.map { |r| r[:path].length }.max]
+ [routes.map { |r| r[:name].length }.max || 0,
+ routes.map { |r| r[:verb].length }.max || 0,
+ routes.map { |r| r[:path].length }.max || 0]
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 18f37dc732..77718a14c1 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -357,6 +357,10 @@ module ActionDispatch
# # params[:category] = 'rock/classic'
# # params[:title] = 'stairway-to-heaven'
#
+ # To match a wildcard parameter, it must have a name assigned to it.
+ # Without a variable name to attach the glob parameter to, the route
+ # can't be parsed.
+ #
# When a pattern points to an internal route, the route's +:action+ and
# +:controller+ should be set in options or hash shorthand. Examples:
#
@@ -707,6 +711,11 @@ module ActionDispatch
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
+ unless nested_scope?
+ options[:shallow_path] ||= options[:path] if options.key?(:path)
+ options[:shallow_prefix] ||= options[:as] if options.key?(:as)
+ end
+
if options[:constraints].is_a?(Hash)
defaults = options[:constraints].select do
|k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
@@ -788,9 +797,16 @@ module ActionDispatch
# end
def namespace(path, options = {})
path = path.to_s
- options = { :path => path, :as => path, :module => path,
- :shallow_path => path, :shallow_prefix => path }.merge!(options)
- scope(options) { yield }
+
+ defaults = {
+ module: path,
+ path: options.fetch(:path, path),
+ as: options.fetch(:as, path),
+ shallow_path: options.fetch(:path, path),
+ shallow_prefix: options.fetch(:as, path)
+ }
+
+ scope(defaults.merge!(options)) { yield }
end
# === Parameter Restriction
@@ -979,6 +995,7 @@ module ActionDispatch
@as = options[:as]
@param = (options[:param] || :id).to_sym
@options = options
+ @shallow = false
end
def default_actions
@@ -1039,6 +1056,13 @@ module ActionDispatch
"#{path}/:#{nested_param}"
end
+ def shallow=(value)
+ @shallow = value
+ end
+
+ def shallow?
+ @shallow
+ end
end
class SingletonResource < Resource #:nodoc:
@@ -1319,8 +1343,10 @@ module ActionDispatch
end
with_scope_level(:member) do
- scope(parent_resource.member_scope) do
- yield
+ if shallow?
+ shallow_scope(parent_resource.member_scope) { yield }
+ else
+ scope(parent_resource.member_scope) { yield }
end
end
end
@@ -1343,16 +1369,8 @@ module ActionDispatch
end
with_scope_level(:nested) do
- if shallow?
- with_exclusive_scope do
- if @scope[:shallow_path].blank?
- scope(parent_resource.nested_scope, nested_options) { yield }
- else
- scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
- scope(parent_resource.nested_scope, nested_options) { yield }
- end
- end
- end
+ if shallow? && shallow_nesting_depth > 1
+ shallow_scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(parent_resource.nested_scope, nested_options) { yield }
end
@@ -1369,7 +1387,7 @@ module ActionDispatch
end
def shallow
- scope(:shallow => true, :shallow_path => @scope[:path]) do
+ scope(:shallow => true) do
yield
end
end
@@ -1410,6 +1428,7 @@ module ActionDispatch
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
if using_match_shorthand?(path_without_format, route_options)
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ route_options[:to].tr!("-", "_")
end
decomposed_match(_path, route_options)
@@ -1440,8 +1459,8 @@ module ActionDispatch
path = path_for_action(action, options.delete(:path))
action = action.to_s.dup
- if action =~ /^[\w\/]+$/
- options[:action] ||= action unless action.include?("/")
+ if action =~ /^[\w\-\/]+$/
+ options[:action] ||= action.tr('-', '_') unless action.include?("/")
else
action = nil
end
@@ -1489,6 +1508,13 @@ module ActionDispatch
return true
end
+ if options.delete(:shallow)
+ shallow do
+ send(method, resources.pop, options, &block)
+ end
+ return true
+ end
+
if resource_scope?
nested { send(method, resources.pop, options, &block) }
return true
@@ -1533,6 +1559,10 @@ module ActionDispatch
RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
end
+ def nested_scope? #:nodoc:
+ @scope[:scope_level] == :nested
+ end
+
def with_exclusive_scope
begin
old_name_prefix, old_path = @scope[:as], @scope[:path]
@@ -1546,21 +1576,24 @@ module ActionDispatch
end
end
- def with_scope_level(kind, resource = parent_resource)
+ def with_scope_level(kind)
old, @scope[:scope_level] = @scope[:scope_level], kind
- old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
yield
ensure
@scope[:scope_level] = old
- @scope[:scope_level_resource] = old_resource
end
def resource_scope(kind, resource) #:nodoc:
- with_scope_level(kind, resource) do
- scope(parent_resource.resource_scope) do
- yield
- end
+ resource.shallow = @scope[:shallow]
+ old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
+ @nesting.push(resource)
+
+ with_scope_level(kind) do
+ scope(parent_resource.resource_scope) { yield }
end
+ ensure
+ @nesting.pop
+ @scope[:scope_level_resource] = old_resource
end
def nested_options #:nodoc:
@@ -1572,6 +1605,14 @@ module ActionDispatch
options
end
+ def nesting_depth #:nodoc:
+ @nesting.size
+ end
+
+ def shallow_nesting_depth #:nodoc:
+ @nesting.select(&:shallow?).size
+ end
+
def param_constraint? #:nodoc:
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
end
@@ -1584,18 +1625,20 @@ module ActionDispatch
flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scoping? #:nodoc:
- shallow? && @scope[:scope_level] == :member
+ def shallow_scope(path, options = {}) #:nodoc:
+ old_name_prefix, old_path = @scope[:as], @scope[:path]
+ @scope[:as], @scope[:path] = @scope[:shallow_prefix], @scope[:shallow_path]
+
+ scope(path, options) { yield }
+ ensure
+ @scope[:as], @scope[:path] = old_name_prefix, old_path
end
def path_for_action(action, path) #:nodoc:
- prefix = shallow_scoping? ?
- "#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path]
-
if canonical_action?(action, path.blank?)
- prefix.to_s
+ @scope[:path].to_s
else
- "#{prefix}/#{action_path(action, path)}"
+ "#{@scope[:path]}/#{action_path(action, path)}"
end
end
@@ -1606,10 +1649,11 @@ module ActionDispatch
def prefix_name_for_action(as, action) #:nodoc:
if as
- as.to_s
+ prefix = as
elsif !canonical_action?(action, @scope[:scope_level])
- action.to_s
+ prefix = action
end
+ prefix.to_s.tr('-', '_') if prefix
end
def name_for_action(as, action) #:nodoc:
@@ -1632,7 +1676,7 @@ module ActionDispatch
when :new
[prefix, :new, name_prefix, member_name]
when :member
- [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
+ [prefix, name_prefix, member_name]
when :root
[name_prefix, collection_name, prefix]
else
@@ -1773,6 +1817,7 @@ module ActionDispatch
@set = set
@scope = { :path_names => @set.resources_path_names }
@concerns = {}
+ @nesting = []
end
include Base
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 2fb03f2712..cfd33d1f31 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -101,10 +101,12 @@ module ActionDispatch
# polymorphic_url(Comment) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
+ recipient = self
+
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
- proxy = record_or_hash_or_array.shift
+ recipient = record_or_hash_or_array.shift
end
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
@@ -130,7 +132,7 @@ module ActionDispatch
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
- named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
+ named_route = build_named_route_call(record_or_hash_or_array, record, inflection, options)
url_options = options.except(:action, :routing_type)
unless url_options.empty?
@@ -139,7 +141,7 @@ module ActionDispatch
args.collect! { |a| convert_to_model(a) }
- (proxy || self).send(named_route, *args)
+ recipient.send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
@@ -173,7 +175,7 @@ module ActionDispatch
options[:routing_type] || :url
end
- def build_named_route_call(records, inflection, options = {})
+ def build_named_route_call(records, record, inflection, options = {})
if records.is_a?(Array)
record = records.pop
route = records.map do |parent|
@@ -184,7 +186,6 @@ module ActionDispatch
end
end
else
- record = extract_record(records)
route = []
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a03fb4cee7..1ec6fa674b 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -155,7 +155,7 @@ module ActionDispatch
end
def self.optimize_helper?(route)
- route.requirements.except(:controller, :action).empty?
+ !route.glob? && route.requirements.except(:controller, :action).empty?
end
class OptimizedUrlHelper < UrlHelper # :nodoc:
@@ -194,7 +194,7 @@ module ActionDispatch
end
def replace_segment(params, segment)
- Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
+ Symbol === segment ? @klass.escape_segment(params[segment]) : segment
end
def optimize_routes_generation?(t)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 3253a3d424..12023e6f77 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -267,7 +267,7 @@ module ActionDispatch
text.strip! unless NO_STRIP.include?(match.name)
text.sub!(/\A\n/, '') if match.name == "textarea"
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text)
true
end
end
@@ -276,7 +276,7 @@ module ActionDispatch
html = match.children.map(&:to_s).join
html.strip! unless NO_STRIP.include?(match.name)
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html)
true
end
end
@@ -289,9 +289,9 @@ module ActionDispatch
# FIXME: minitest provides messaging when we use assert_operator,
# so is this custom message really needed?
- message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.)
+ message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size})
if count
- assert_equal matches.size, count, message
+ assert_equal count, matches.size, message
else
assert_operator matches.size, :>=, min, message if min
assert_operator matches.size, :<=, max, message if max
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
new file mode 100644
index 0000000000..beaf35d3da
--- /dev/null
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -0,0 +1,15 @@
+module ActionPack
+ # Returns the version of the currently loaded ActionPack as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index a51f6a434a..7088cd2760 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActionPack
- # Returns the version of the currently loaded ActionPack as a Gem::Version
+ # Returns the version of the currently loaded ActionPack as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActionPack.version.segments
- STRING = ActionPack.version.to_s
+ gem_version
end
end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 114bbf3c22..580604df3a 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -79,13 +79,13 @@ class AssertSelectTest < ActionController::TestCase
def test_assert_select
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_select "div", 2
- assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
+ assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" }
end
def test_equality_integer
render_html %Q{<div id="1"></div><div id="2"></div>}
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
- assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 }
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 }
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 }
end
def test_equality_true_false
@@ -100,13 +100,14 @@ class AssertSelectTest < ActionController::TestCase
def test_equality_false_message
render_html %Q{<div id="1"></div><div id="2"></div>}
- assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false }
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false }
end
def test_equality_string_and_regexp
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", "foo" }
assert_raise(Assertion) { assert_select "div", "bar" }
+ assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" }
assert_nothing_raised { assert_select "div", :text=>"foo" }
assert_raise(Assertion) { assert_select "div", :text=>"bar" }
assert_nothing_raised { assert_select "div", /(foo|bar)/ }
@@ -124,6 +125,7 @@ class AssertSelectTest < ActionController::TestCase
assert_raise(Assertion) { assert_select "p", html }
assert_nothing_raised { assert_select "p", :html=>html }
assert_raise(Assertion) { assert_select "p", :html=>text }
+ assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text }
# No stripping for pre.
render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>}
text = "\n\"This is not a big problem,\" he said.\n"
@@ -144,29 +146,29 @@ class AssertSelectTest < ActionController::TestCase
def test_counts
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", 2 }
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
assert_select "div", 3
end
assert_nothing_raised { assert_select "div", 1..2 }
- assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
assert_select "div", 3..4
end
assert_nothing_raised { assert_select "div", :count=>2 }
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
assert_select "div", :count=>3
end
assert_nothing_raised { assert_select "div", :minimum=>1 }
assert_nothing_raised { assert_select "div", :minimum=>2 }
- assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do
assert_select "div", :minimum=>3
end
assert_nothing_raised { assert_select "div", :maximum=>2 }
assert_nothing_raised { assert_select "div", :maximum=>3 }
- assert_failure(/Expected at most 1 element matching \"div\", found 2/) do
+ assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do
assert_select "div", :maximum=>1
end
assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 }
- assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
assert_select "div", :minimum=>3, :maximum=>4
end
end
@@ -204,7 +206,7 @@ class AssertSelectTest < ActionController::TestCase
end
end
- assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
+ assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do
assert_select "div" do
assert_select "#4"
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 57b45b8f7b..58a86ce9af 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -164,6 +164,13 @@ class FunctionalCachingController < CachingController
end
end
+ def formatted_fragment_cached_with_variant
+ respond_to do |format|
+ format.html.phone
+ format.html
+ end
+ end
+
def fragment_cached_without_digest
end
end
@@ -190,7 +197,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "This bit's fragment cached",
- @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached", "html")}")
+ @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}")
end
def test_fragment_caching_in_partials
@@ -199,7 +206,7 @@ CACHED
assert_match(/Old fragment caching in a partial/, @response.body)
assert_match("Old fragment caching in a partial",
- @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial", "html")}"))
+ @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}"))
end
def test_skipping_fragment_cache_digesting
@@ -217,7 +224,7 @@ CACHED
assert_match(/Some inline content/, @response.body)
assert_match(/Some cached content/, @response.body)
assert_match("Some cached content",
- @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached", "html")}"))
+ @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}"))
end
def test_html_formatted_fragment_caching
@@ -228,7 +235,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "<p>ERB</p>",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached", "html")}")
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
end
def test_xml_formatted_fragment_caching
@@ -239,12 +246,26 @@ CACHED
assert_equal expected_body, @response.body
assert_equal " <p>Builder</p>\n",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached", "xml")}")
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ end
+
+
+ def test_fragment_caching_with_variant
+ @request.variant = :phone
+
+ get :formatted_fragment_cached_with_variant, :format => "html"
+ assert_response :success
+ expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+
+ assert_equal "<p>PHONE</p>",
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}")
end
private
- def template_digest(name, format)
- ActionView::Digestor.digest(name, format, @controller.lookup_context)
+ def template_digest(name)
+ ActionView::Digestor.digest(name: name, finder: @controller.lookup_context)
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index d3efca5b6f..c87494aa64 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -225,6 +225,10 @@ class FilterTest < ActionController::TestCase
skip_before_filter :clean_up_tmp, if: -> { true }
end
+ class ClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter
+ end
+
class PrependingController < TestController
prepend_before_filter :wonderful_life
# skip_before_filter :fire_flash
@@ -610,6 +614,18 @@ class FilterTest < ActionController::TestCase
assert_equal %w( ensure_login ), assigns["ran_filter"]
end
+ def test_skipping_class_filters
+ test_process(ClassController)
+ assert_equal true, assigns["ran_class_filter"]
+
+ skipping_class_controller = Class.new(ClassController) do
+ skip_before_filter ConditionalClassFilter
+ end
+
+ test_process(skipping_class_controller)
+ assert_nil assigns['ran_class_filter']
+ end
+
def test_running_collection_condition_filters
test_process(ConditionalCollectionFilterController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 5490d9394b..50b36a0567 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -67,6 +67,16 @@ module ActionDispatch
assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
end
+ def test_from_session_value_on_json_serializer
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+
+ assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
+ assert_equal "hey you", hash[:message]
+ assert_equal "hey you", hash["message"]
+ end
+
def test_empty?
assert @hash.empty?
@hash['zomg'] = 'bears'
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 9ceab91e42..3720a920d0 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -175,13 +175,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
@@ -210,20 +210,29 @@ class FlashTest < ActionController::TestCase
end
def test_redirect_to_with_adding_flash_types
- @controller.class.add_flash_types :foo
+ original_controller = @controller
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ @controller = test_controller_with_flash_type_foo.new
get :redirect_with_foo_flash
assert_equal "for great justice", @controller.send(:flash)[:foo]
+ ensure
+ @controller = original_controller
end
- class SubclassesTestController < TestController; end
-
def test_add_flash_type_to_subclasses
- TestController.add_flash_types :foo
- assert SubclassesTestController._flash_types.include?(:foo)
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo)
+ assert subclass_controller_with_no_flash_type._flash_types.include?(:foo)
end
- def test_do_not_add_flash_type_to_parent_class
- SubclassesTestController.add_flash_types :bar
+ def test_does_not_add_flash_type_to_parent_class
+ Class.new(TestController) do
+ add_flash_types :bar
+ end
assert_not TestController._flash_types.include?(:bar)
end
end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index ebf6d224aa..86b94652ce 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -21,7 +21,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
private
def authenticate
- authenticate_or_request_with_http_token do |token, options|
+ authenticate_or_request_with_http_token do |token, _|
token == 'lifo'
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0a431270b5..947f64176b 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/concurrency/latch'
+Thread.abort_on_exception = true
module ActionController
class SSETest < ActionController::TestCase
@@ -43,9 +44,7 @@ module ActionController
tests SSETestController
def wait_for_response_stream_close
- while !response.stream.closed?
- sleep 0.01
- end
+ response.body
end
def test_basic_sse
@@ -91,6 +90,9 @@ module ActionController
end
class LiveStreamTest < ActionController::TestCase
+ class Exception < StandardError
+ end
+
class TestController < ActionController::Base
include ActionController::Live
@@ -100,6 +102,12 @@ module ActionController
'test'
end
+ def set_cookie
+ cookies[:hello] = "world"
+ response.stream.write "hello world"
+ response.close
+ end
+
def render_text
render :text => 'zomg'
end
@@ -145,6 +153,11 @@ module ActionController
render 'doesntexist'
end
+ def exception_in_view_after_commit
+ response.stream.write ""
+ render 'doesntexist'
+ end
+
def exception_with_callback
response.headers['Content-Type'] = 'text/event-stream'
@@ -153,32 +166,33 @@ module ActionController
response.stream.close
end
+ response.stream.write "" # make sure the response is committed
raise 'An exception occurred...'
end
+ def exception_in_controller
+ raise Exception, 'Exception in controller'
+ end
+
+ def bad_request_error
+ raise ActionController::BadRequest
+ end
+
def exception_in_exception_callback
response.headers['Content-Type'] = 'text/event-stream'
response.stream.on_error do
raise 'We need to go deeper.'
end
+ response.stream.write ''
response.stream.write params[:widget][:didnt_check_for_nil]
end
end
tests TestController
- class TestResponse < Live::Response
- def recycle!
- initialize
- end
- end
-
- def build_response
- TestResponse.new
- end
-
def assert_stream_closed
assert response.stream.closed?, 'stream should be closed'
+ assert response.sent?, 'stream should be sent'
end
def capture_log_output
@@ -192,6 +206,13 @@ module ActionController
end
end
+ def test_set_cookie
+ @controller = TestController.new
+ get :set_cookie
+ assert_equal({'hello' => 'world'}, @response.cookies)
+ assert_equal "hello world", @response.body
+ end
+
def test_set_response!
@controller.set_response!(@request)
assert_kind_of(Live::Response, @controller.response)
@@ -213,6 +234,7 @@ module ActionController
@controller.response = @response
t = Thread.new(@response) { |resp|
+ resp.await_commit
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
@@ -249,24 +271,34 @@ module ActionController
end
def test_exception_handling_html
- capture_log_output do |output|
+ assert_raises(ActionView::MissingTemplate) do
get :exception_in_view
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit
assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
assert_match 'Missing template test/doesntexist', output.rewind && output.read
assert_stream_closed
end
+ assert response.body
+ assert_stream_closed
end
def test_exception_handling_plain_text
- capture_log_output do |output|
+ assert_raises(ActionView::MissingTemplate) do
get :exception_in_view, format: :json
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit, format: :json
assert_equal '', response.body
assert_match 'Missing template test/doesntexist', output.rewind && output.read
assert_stream_closed
end
end
- def test_exception_callback
+ def test_exception_callback_when_committed
capture_log_output do |output|
get :exception_with_callback, format: 'text/event-stream'
assert_equal %(data: "500 Internal Server Error"\n\n), response.body
@@ -275,7 +307,19 @@ module ActionController
end
end
- def test_exceptions_raised_handling_exceptions
+ def test_exception_in_controller_before_streaming
+ assert_raises(ActionController::LiveStreamTest::Exception) do
+ get :exception_in_controller, format: 'text/event-stream'
+ end
+ end
+
+ def test_bad_request_in_controller_before_streaming
+ assert_raises(ActionController::BadRequest) do
+ get :bad_request_error, format: 'text/event-stream'
+ end
+ end
+
+ def test_exceptions_raised_handling_exceptions_and_committed
capture_log_output do |output|
get :exception_in_exception_callback, format: 'text/event-stream'
assert_equal '', response.body
@@ -295,4 +339,11 @@ module ActionController
assert_equal 304, @response.status.to_i
end
end
+
+ class BufferTest < ActionController::TestCase
+ def test_nil_callback
+ buf = ActionController::Live::Buffer.new nil
+ assert buf.call_on_error
+ end
+ end
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 075347be52..18037b3d2f 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -137,6 +137,17 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal 'Parameters: {"id"=>"10"}', logs[1]
end
+ def test_multiple_process_with_parameters
+ get :show, :id => '10'
+ get :show, :id => '20'
+
+ wait
+
+ assert_equal 6, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ assert_equal 'Parameters: {"id"=>"20"}', logs[4]
+ end
+
def test_process_action_with_wrapped_parameters
@request.env['CONTENT_TYPE'] = 'application/json'
post :show, :id => '10', :name => 'jose'
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 84e4936f31..ce6d135d92 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -492,6 +492,11 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'Whatever you ask for, I got it', @response.body
end
+ def test_handle_any_any_unkown_format
+ get :handle_any_any, { format: 'php' }
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
def test_browser_check_with_any_any
@request.accept = "application/json, application/xml"
get :json_xml_or_html
@@ -671,6 +676,10 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_variant_any_any
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
@request.variant = :phone
get :variant_any_any
assert_equal "text/html", @response.content_type
@@ -740,4 +749,25 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/javascript", @response.content_type
assert_equal "tablet", @response.body
end
+
+ def test_variant_negotiation_inline_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_block_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_without_block
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
end
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
new file mode 100644
index 0000000000..fad848349a
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -0,0 +1,170 @@
+require 'abstract_unit'
+
+module RenderBody
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render body: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render body: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render body: "hello david"
+ end
+
+ def custom_code
+ render body: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render body: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render body: nil
+ end
+
+ def with_nil_and_status
+ render body: nil, status: 403
+ end
+
+ def with_false
+ render body: false
+ end
+
+ def with_layout_true
+ render body: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render body: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render body: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render body: "hello world", layout: "greetings"
+ end
+
+ def with_custom_content_type
+ response.headers['Content-Type'] = 'application/json'
+ render body: '["troll","face"]'
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render body: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderBodyTest < Rack::TestCase
+ test "rendering body from a minimal controller" do
+ get "/render_body/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering body from an action with default options renders the body with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body from an action with default options renders the body without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body, while also providing a custom status code" do
+ get "/render_body/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering body with nil returns an empty body padded for Safari" do
+ get "/render_body/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering body with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_body/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering body with false returns the string 'false'" do
+ get "/render_body/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering body with layout: true" do
+ get "/render_body/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering body with layout: 'greetings'" do
+ get "/render_body/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "specified content type should not be removed" do
+ get "/render_body/with_layout/with_custom_content_type"
+
+ assert_equal %w{ troll face }, JSON.parse(response.body)
+ assert_equal 'application/json', response.headers['Content-Type']
+ end
+
+ test "rendering body with layout: false" do
+ get "/render_body/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering body with layout: nil" do
+ get "/render_body/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
new file mode 100644
index 0000000000..bfe0271df7
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -0,0 +1,190 @@
+require 'abstract_unit'
+
+module RenderHtml
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render html: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render html: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render html: "hello david"
+ end
+
+ def custom_code
+ render html: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render html: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render html: nil
+ end
+
+ def with_nil_and_status
+ render html: nil, status: 403
+ end
+
+ def with_false
+ render html: false
+ end
+
+ def with_layout_true
+ render html: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render html: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render html: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render html: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render html: "hello world", layout: "ivar"
+ end
+
+ def with_unsafe_html_tag
+ render html: "<p>hello world</p>", layout: nil
+ end
+
+ def with_safe_html_tag
+ render html: "<p>hello world</p>".html_safe, layout: nil
+ end
+ end
+
+ class RenderHtmlTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_html/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_html/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_html/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_html/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_html/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_html/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_html/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_html/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_html/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering html should escape the string if it is not html safe" do
+ get "/render_html/with_layout/with_unsafe_html_tag"
+
+ assert_body "&lt;p&gt;hello world&lt;/p&gt;"
+ assert_status 200
+ end
+
+ test "rendering html should not escape the string if it is html safe" do
+ get "/render_html/with_layout/with_safe_html_tag"
+
+ assert_body "<p>hello world</p>"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/html content type" do
+ get "/render_html/minimal/index"
+ assert_content_type "text/html"
+ end
+
+ test "rendering from normal controller returns response with text/html content type" do
+ get "/render_html/simple/index"
+ assert_content_type "text/html; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
new file mode 100644
index 0000000000..dba2e9f13e
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -0,0 +1,168 @@
+require 'abstract_unit'
+
+module RenderPlain
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render plain: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render plain: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.text.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render plain: "hello david"
+ end
+
+ def custom_code
+ render plain: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render plain: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render plain: nil
+ end
+
+ def with_nil_and_status
+ render plain: nil, status: 403
+ end
+
+ def with_false
+ render plain: false
+ end
+
+ def with_layout_true
+ render plain: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render plain: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render plain: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render plain: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render plain: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderPlainTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_plain/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_plain/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_plain/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_plain/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_plain/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_plain/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_plain/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_plain/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_plain/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/plain content type" do
+ get "/render_plain/minimal/index"
+ assert_content_type "text/plain"
+ end
+
+ test "rendering from normal controller returns response with text/plain content type" do
+ get "/render_plain/simple/index"
+ assert_content_type "text/plain; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 2a253799f3..abb81d7e71 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -14,7 +14,7 @@ module RenderText
self.view_paths = [ActionView::FixtureResolver.new]
def index
- render :text => "hello david"
+ render text: "hello david"
end
end
@@ -26,48 +26,48 @@ module RenderText
)]
def index
- render :text => "hello david"
+ render text: "hello david"
end
def custom_code
- render :text => "hello world", :status => 404
+ render text: "hello world", status: 404
end
def with_custom_code_as_string
- render :text => "hello world", :status => "404 Not Found"
+ render text: "hello world", status: "404 Not Found"
end
def with_nil
- render :text => nil
+ render text: nil
end
def with_nil_and_status
- render :text => nil, :status => 403
+ render text: nil, status: 403
end
def with_false
- render :text => false
+ render text: false
end
def with_layout_true
- render :text => "hello world", :layout => true
+ render text: "hello world", layout: true
end
def with_layout_false
- render :text => "hello world", :layout => false
+ render text: "hello world", layout: false
end
def with_layout_nil
- render :text => "hello world", :layout => nil
+ render text: "hello world", layout: nil
end
def with_custom_layout
- render :text => "hello world", :layout => "greetings"
+ render text: "hello world", layout: "greetings"
end
def with_ivar_in_layout
@ivar = "hello world"
- render :text => "hello world", :layout => "ivar"
+ render text: "hello world", layout: "ivar"
end
end
@@ -80,7 +80,7 @@ module RenderText
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/simple"
assert_body "hello david"
@@ -90,7 +90,7 @@ module RenderText
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/with_layout"
@@ -127,28 +127,28 @@ module RenderText
assert_status 200
end
- test "rendering text with :layout => true" do
+ test "rendering text with layout: true" do
get "/render_text/with_layout/with_layout_true"
assert_body "hello world, I'm here!"
assert_status 200
end
- test "rendering text with :layout => 'greetings'" do
+ test "rendering text with layout: 'greetings'" do
get "/render_text/with_layout/with_custom_layout"
assert_body "hello world, I wish thee well."
assert_status 200
end
- test "rendering text with :layout => false" do
+ test "rendering text with layout: false" do
get "/render_text/with_layout/with_layout_false"
assert_body "hello world"
assert_status 200
end
- test "rendering text with :layout => nil" do
+ test "rendering text with layout: nil" do
get "/render_text/with_layout/with_layout_nil"
assert_body "hello world"
diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
index 22e603b881..9ce04b9aeb 100644
--- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
+++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
@@ -10,23 +10,45 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
ActionController::Parameters.action_on_unpermitted_parameters = false
end
- test "logs on unexpected params" do
+ test "logs on unexpected param" do
params = ActionController::Parameters.new({
book: { pages: 65 },
fishing: "Turnips"
})
- assert_logged("Unpermitted parameters: fishing") do
+ assert_logged("Unpermitted parameter: fishing") do
params.permit(book: [:pages])
end
end
- test "logs on unexpected nested params" do
+ test "logs on unexpected params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ fishing: "Turnips",
+ car: "Mersedes"
+ })
+
+ assert_logged("Unpermitted parameters: fishing, car") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "logs on unexpected nested param" do
params = ActionController::Parameters.new({
book: { pages: 65, title: "Green Cats and where to find then." }
})
- assert_logged("Unpermitted parameters: title") do
+ assert_logged("Unpermitted parameter: title") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "logs on unexpected nested params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" }
+ })
+
+ assert_logged("Unpermitted parameters: title, author") do
params.permit(book: [:pages])
end
end
diff --git a/actionpack/test/controller/parameters/parameters_require_test.rb b/actionpack/test/controller/parameters/parameters_require_test.rb
deleted file mode 100644
index bdaba8d2d8..0000000000
--- a/actionpack/test/controller/parameters/parameters_require_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'abstract_unit'
-require 'action_controller/metal/strong_parameters'
-
-class ParametersRequireTest < ActiveSupport::TestCase
- test "required parameters must be present not merely not nil" do
- assert_raises(ActionController::ParameterMissing) do
- ActionController::Parameters.new(person: {}).require(:person)
- end
- end
-end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index d87e2b85b0..11ccb6cf3b 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -188,6 +188,26 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
+
+ def test_preserves_query_string_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ get :parse, { 'user' => { 'username' => 'nixon' } }
+ assert_parameters(
+ {'user' => { 'username' => 'nixon' } }
+ )
+ end
+ end
+
+ def test_empty_parameter_set
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, {}
+ assert_parameters(
+ {'user' => { } }
+ )
+ end
+ end
end
class NamespacedParamsWrapperTest < ActionController::TestCase
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 1f5fc06410..5ab5141966 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -138,14 +138,14 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :index
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_button_to_with_token_tag
assert_not_blocked do
get :show_button
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_form_without_token_tag_if_remote
@@ -175,7 +175,7 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :form_for_remote_with_external_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
ensure
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
end
@@ -185,21 +185,21 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :form_for_remote_with_external_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
end
def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
assert_not_blocked do
get :form_for_remote_with_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_form_with_token_tag_with_authenticity_token_requested
assert_not_blocked do
get :form_for_with_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_allow_get
@@ -289,6 +289,22 @@ module RequestForgeryProtectionTests
end
end
+ def test_should_not_warn_if_csrf_logging_disabled
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+ ActionController::Base.log_warning_on_csrf_failure = false
+
+ begin
+ assert_blocked { post :index }
+
+ assert_equal 0, logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = old_logger
+ ActionController::Base.log_warning_on_csrf_failure = true
+ end
+ end
+
def test_should_only_allow_same_origin_js_get_with_xhr_header
assert_cross_origin_blocked { get :same_origin_js }
assert_cross_origin_blocked { get :same_origin_js, format: 'js' }
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 343d57c300..25d0337212 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -25,3 +25,11 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
assert_response :ok
end
end
+
+class ParametersRequireTest < ActiveSupport::TestCase
+ test "required parameters must be present not merely not nil" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: {}).require(:person)
+ end
+ end
+end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index de0476dbde..fbc10baf21 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -163,6 +163,29 @@ XML
end
end
+ class DefaultUrlOptionsCachingController < ActionController::Base
+ before_filter { @dynamic_opt = 'opt' }
+
+ def test_url_options_reset
+ render text: url_for(params)
+ end
+
+ def default_url_options
+ if defined?(@dynamic_opt)
+ super.merge dynamic_opt: @dynamic_opt
+ else
+ super
+ end
+ end
+ end
+
+ def test_url_options_reset
+ @controller = DefaultUrlOptionsCachingController.new
+ get :test_url_options_reset
+ assert_nil @request.params['dynamic_opt']
+ assert_match(/dynamic_opt=opt/, @response.body)
+ end
+
def test_raw_post_handling
params = Hash[:page, {:name => 'page name'}, 'some key', 123]
post :render_raw_post, params.dup
@@ -706,6 +729,14 @@ XML
assert @request.params[:foo].blank?
end
+ def test_filtered_parameters_reset_between_requests
+ get :no_op, :foo => "bar"
+ assert_equal "bar", @request.filtered_parameters[:foo]
+
+ get :no_op, :foo => "baz"
+ assert_equal "baz", @request.filtered_parameters[:foo]
+ end
+
def test_symbolized_path_params_reset_after_request
get :test_params, :id => "foo"
assert_equal "foo", @request.symbolized_path_parameters[:id]
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index d2b4952759..a8035e5bd7 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -204,9 +204,6 @@ module AbstractController
end
def test_relative_url_root_is_respected
- # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This
- # should probably come from routes.
-
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 => '/subdir')
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 91ac13e7c6..0f145666d1 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -11,6 +11,16 @@ require 'active_support/key_generator'
require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
+ class CustomSerializer
+ def self.load(value)
+ value.to_s + " and loaded"
+ end
+
+ def self.dump(value)
+ value.to_s + " was dumped"
+ end
+ end
+
class TestController < ActionController::Base
def authenticate
cookies["user_name"] = "david"
@@ -359,9 +369,72 @@ class CookiesTest < ActionController::TestCase
assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
end
- def test_signed_cookie
+ def test_signed_cookie_using_default_serializer
get :set_signed_cookie
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_signed_cookie
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal '45 was dumped and loaded', cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45)
+ @request.headers["Cookie"] = "user_id=#{marshal_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies['user_id'])
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+ json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45)
+ @request.headers["Cookie"] = "user_id=#{json_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ assert_nil @response.cookies["user_id"]
end
def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
@@ -369,7 +442,18 @@ class CookiesTest < ActionController::TestCase
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
end
- def test_encrypted_cookie
+ def test_encrypted_cookie_using_default_serializer
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raise TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal 'bar', cookies[:foo]
@@ -379,6 +463,66 @@ class CookiesTest < ActionController::TestCase
assert_equal 'bar', cookies.encrypted[:foo]
end
+ def test_encrypted_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises ::JSON::ParserError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_encrypted_cookie
+ assert_not_equal 'bar', cookies.encrypted[:foo]
+ assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{json_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ assert_nil @response.cookies["foo"]
+ end
+
def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
@@ -537,6 +681,123 @@ class CookiesTest < ActionController::TestCase
assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
end
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@@ -694,8 +955,6 @@ class CookiesTest < ActionController::TestCase
assert_equal "dhh", cookies['user_name']
end
-
-
def test_setting_request_cookies_is_indifferent_access
cookies.clear
cookies[:user_name] = "andrew"
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 3045a07ad6..8660deb634 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -43,6 +43,19 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise ActionController::UrlGenerationError, "No route matches"
when "/parameter_missing"
raise ActionController::ParameterMissing, :missing_param_key
+ when "/original_syntax_error"
+ eval 'broke_syntax =' # `eval` need for raise native SyntaxError at runtime
+ when "/syntax_error_into_view"
+ begin
+ eval 'broke_syntax ='
+ rescue Exception => e
+ template = ActionView::Template.new(File.read(__FILE__),
+ __FILE__,
+ ActionView::Template::Handlers::Raw.new,
+ {})
+ raise ActionView::Template::Error.new(template, e)
+ end
+
else
raise "puke!"
end
@@ -134,9 +147,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, xhr_request_env
assert_response 500
+ assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal response.content_type, "text/plain"
- assert_match(/puke/, body)
+ assert_match(/RuntimeError\npuke/, body)
get "/not_found", {}, xhr_request_env
assert_response 404
@@ -242,4 +256,26 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, env
assert_operator((output.rewind && output.read).lines.count, :>, 10)
end
+
+ test 'display backtrace when error type is SyntaxError' do
+ @app = DevelopmentApp
+
+ get '/original_syntax_error', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+
+ assert_response 500
+ assert_select '#Application-Trace' do
+ assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ end
+ end
+
+ test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do
+ @app = DevelopmentApp
+
+ get '/syntax_error_into_view', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+
+ assert_response 500
+ assert_select '#Application-Trace' do
+ assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ end
+ end
end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index e0cfb73acf..512f3a8a7a 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -6,6 +6,7 @@ module ActionController
class ResponseTest < ActiveSupport::TestCase
def setup
@response = Live::Response.new
+ @response.request = ActionDispatch::Request.new({}) #yolo
end
def test_header_merge
@@ -34,6 +35,7 @@ module ActionController
@response.stream.close
}
+ @response.await_commit
@response.each do |part|
assert_equal 'foo', part
latch.release
@@ -58,21 +60,30 @@ module ActionController
assert_nil @response.headers['Content-Length']
end
- def test_headers_cannot_be_written_after_write
+ def test_headers_cannot_be_written_after_webserver_reads
@response.stream.write 'omg'
+ latch = ActiveSupport::Concurrency::Latch.new
+ t = Thread.new {
+ @response.stream.each do |chunk|
+ latch.release
+ end
+ }
+
+ latch.await
assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
end
assert_equal 'header already sent', e.message
+ @response.stream.close
+ t.join
end
def test_headers_cannot_be_written_after_close
@response.stream.close
- assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 683a4f01e2..cdf00d84fb 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -44,11 +44,6 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
"A named route should be defined with a parent's prefix"
end
- def test_trailing_slash_is_not_removed_from_path_info
- get "/sprockets/omg/"
- assert_equal "/sprockets -- /omg/", response.body
- end
-
def test_mounting_sets_script_name
get "/sprockets/omg"
assert_equal "/sprockets -- /omg", response.body
diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb
deleted file mode 100644
index 42067854ee..0000000000
--- a/actionpack/test/dispatch/rack_test.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-require 'abstract_unit'
-
-# TODO: Merge these tests into RequestTest
-
-class BaseRackTest < ActiveSupport::TestCase
- def setup
- @env = {
- "HTTP_MAX_FORWARDS" => "10",
- "SERVER_NAME" => "glu.ttono.us",
- "FCGI_ROLE" => "RESPONDER",
- "AUTH_TYPE" => "Basic",
- "HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
- "HTTP_ACCEPT_CHARSET" => "UTF-8",
- "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
- "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
- "HTTP_PRAGMA" => "no-cache",
- "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
- "PATH_INFO" => "/homepage/",
- "HTTP_ACCEPT_LANGUAGE" => "en",
- "HTTP_NEGOTIATE" => "trans",
- "HTTP_HOST" => "glu.ttono.us:8007",
- "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
- "HTTP_FROM" => "googlebot",
- "SERVER_PROTOCOL" => "HTTP/1.1",
- "REDIRECT_URI" => "/dispatch.fcgi",
- "SCRIPT_NAME" => "/dispatch.fcgi",
- "SERVER_ADDR" => "207.7.108.53",
- "REMOTE_ADDR" => "207.7.108.53",
- "REMOTE_HOST" => "google.com",
- "REMOTE_IDENT" => "kevin",
- "REMOTE_USER" => "kevin",
- "SERVER_SOFTWARE" => "lighttpd/1.4.5",
- "HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes",
- "HTTP_X_FORWARDED_SERVER" => "glu.ttono.us",
- "REQUEST_URI" => "/admin",
- "DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public",
- "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
- "SERVER_PORT" => "8007",
- "QUERY_STRING" => "",
- "REMOTE_PORT" => "63137",
- "GATEWAY_INTERFACE" => "CGI/1.1",
- "HTTP_X_FORWARDED_FOR" => "65.88.180.234",
- "HTTP_ACCEPT" => "*/*",
- "SCRIPT_FILENAME" => "/home/kevinc/sites/typo/public/dispatch.fcgi",
- "REDIRECT_STATUS" => "200",
- "REQUEST_METHOD" => "GET"
- }
- @request = ActionDispatch::Request.new(@env)
- # some Nokia phone browsers omit the space after the semicolon separator.
- # some developers have grown accustomed to using comma in cookie values.
- @alt_cookie_fmt_request = ActionDispatch::Request.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}))
- end
-
- private
- def set_content_data(data)
- @request.env['REQUEST_METHOD'] = 'POST'
- @request.env['CONTENT_LENGTH'] = data.length
- @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
- @request.env['rack.input'] = StringIO.new(data)
- end
-end
-
-class RackRequestTest < BaseRackTest
- test "proxy request" do
- assert_equal 'glu.ttono.us', @request.host_with_port
- end
-
- test "http host" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env['HTTP_HOST'] = "rubyonrails.org:8080"
- assert_equal "rubyonrails.org", @request.host
- assert_equal "rubyonrails.org:8080", @request.host_with_port
-
- @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
- assert_equal "www.secondhost.org", @request.host
- end
-
- test "http host with default port overrides server port" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env['HTTP_HOST'] = "rubyonrails.org"
- assert_equal "rubyonrails.org", @request.host_with_port
- end
-
- test "host with port defaults to server name if no host headers" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env.delete "HTTP_HOST"
- assert_equal "glu.ttono.us:8007", @request.host_with_port
- end
-
- test "host with port falls back to server addr if necessary" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env.delete "HTTP_HOST"
- @env.delete "SERVER_NAME"
- assert_equal "207.7.108.53", @request.host
- assert_equal 8007, @request.port
- assert_equal "207.7.108.53:8007", @request.host_with_port
- end
-
- test "host with port if http standard port is specified" do
- @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
- assert_equal "glu.ttono.us", @request.host_with_port
- end
-
- test "host with port if https standard port is specified" do
- @env['HTTP_X_FORWARDED_PROTO'] = "https"
- @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
- assert_equal "glu.ttono.us", @request.host_with_port
- end
-
- test "host if ipv6 reference" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
- end
-
- test "host if ipv6 reference with port" do
- @env.delete "HTTP_X_FORWARDED_HOST"
- @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
- end
-
- test "cgi environment variables" do
- assert_equal "Basic", @request.auth_type
- assert_equal 0, @request.content_length
- assert_equal nil, @request.content_mime_type
- assert_equal "CGI/1.1", @request.gateway_interface
- assert_equal "*/*", @request.accept
- assert_equal "UTF-8", @request.accept_charset
- assert_equal "gzip, deflate", @request.accept_encoding
- assert_equal "en", @request.accept_language
- assert_equal "no-cache, max-age=0", @request.cache_control
- assert_equal "googlebot", @request.from
- assert_equal "glu.ttono.us", @request.host
- assert_equal "trans", @request.negotiate
- assert_equal "no-cache", @request.pragma
- assert_equal "http://www.google.com/search?q=glu.ttono.us", @request.referer
- assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", @request.user_agent
- assert_equal "/homepage/", @request.path_info
- assert_equal "/home/kevinc/sites/typo/public/homepage/", @request.path_translated
- assert_equal "", @request.query_string
- assert_equal "207.7.108.53", @request.remote_addr
- assert_equal "google.com", @request.remote_host
- assert_equal "kevin", @request.remote_ident
- assert_equal "kevin", @request.remote_user
- assert_equal "GET", @request.request_method
- assert_equal "/dispatch.fcgi", @request.script_name
- assert_equal "glu.ttono.us", @request.server_name
- assert_equal 8007, @request.server_port
- assert_equal "HTTP/1.1", @request.server_protocol
- assert_equal "lighttpd", @request.server_software
- end
-
- test "cookie syntax resilience" do
- cookies = @request.cookies
- assert_equal "c84ace84796670c052c6ceb2451fb0f2", cookies["_session_id"], cookies.inspect
- assert_equal "yes", cookies["is_admin"], cookies.inspect
-
- alt_cookies = @alt_cookie_fmt_request.cookies
- #assert_equal "c84ace847,96670c052c6ceb2451fb0f2", alt_cookies["_session_id"], alt_cookies.inspect
- assert_equal "yes", alt_cookies["is_admin"], alt_cookies.inspect
- end
-end
-
-class RackRequestParamsParsingTest < BaseRackTest
- test "doesnt break when content type has charset" do
- set_content_data 'flamenco=love'
-
- assert_equal({"flamenco"=> "love"}, @request.request_parameters)
- end
-
- test "doesnt interpret request uri as query string when missing" do
- @request.env['REQUEST_URI'] = 'foo'
- assert_equal({}, @request.query_parameters)
- end
-end
-
-class RackRequestNeedsRewoundTest < BaseRackTest
- test "body should be rewound" do
- data = 'foo'
- @env['rack.input'] = StringIO.new(data)
- @env['CONTENT_LENGTH'] = data.length
- @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
-
- # Read the request body by parsing params.
- request = ActionDispatch::Request.new(@env)
- request.request_parameters
-
- # Should have rewound the body.
- assert_equal 0, request.body.pos
- end
-end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index dba9ab688f..c609075e6b 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -23,6 +23,13 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
)
end
+ test "parses boolean and number json params for application json" do
+ assert_parses(
+ {"item" => {"enabled" => false, "count" => 10}},
+ "{\"item\": {\"enabled\": false, \"count\": 10}}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
test "parses json params for application jsonrequest" do
assert_parses(
{"person" => {"name" => "David"}},
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index df55fcc8bc..10fb04e230 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -36,29 +36,55 @@ module ActionDispatch
assert_equal s, Session.find(env)
end
+ def test_destroy
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.destroy
+
+ assert_empty s
+ end
+
def test_keys
- env = {}
- s = Session.create(store, env, {})
+ s = Session.create(store, {}, {})
s['rails'] = 'ftw'
s['adequate'] = 'awesome'
assert_equal %w[rails adequate], s.keys
end
def test_values
- env = {}
- s = Session.create(store, env, {})
+ s = Session.create(store, {}, {})
s['rails'] = 'ftw'
s['adequate'] = 'awesome'
assert_equal %w[ftw awesome], s.values
end
def test_clear
- env = {}
- s = Session.create(store, env, {})
+ s = Session.create(store, {}, {})
s['rails'] = 'ftw'
s['adequate'] = 'awesome'
+
s.clear
- assert_equal([], s.values)
+ assert_empty(s.values)
+ end
+
+ def test_update
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.update(:rails => 'awesome')
+
+ assert_equal(['rails'], s.keys)
+ assert_equal('awesome', s['rails'])
+ end
+
+ def test_delete
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.delete('rails')
+
+ assert_empty(s.keys)
end
def test_fetch
@@ -82,6 +108,7 @@ module ActionDispatch
Class.new {
def load_session(env); [1, {}]; end
def session_exists?(env); true; end
+ def destroy_session(env, id, options); 123; end
}.new
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f79fe47897..6e21b4a258 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -1,12 +1,34 @@
require 'abstract_unit'
-class RequestTest < ActiveSupport::TestCase
+class BaseRequestTest < ActiveSupport::TestCase
+ def setup
+ @env = {
+ :ip_spoofing_check => true,
+ :tld_length => 1,
+ "rack.input" => "foo"
+ }
+ end
def url_for(options = {})
options = { host: 'www.example.com' }.merge!(options)
ActionDispatch::Http::URL.url_for(options)
end
+ protected
+ def stub_request(env = {})
+ ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true
+ @trusted_proxies ||= nil
+ ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies)
+ tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1
+ ip_app.call(env)
+ ActionDispatch::Http::URL.tld_length = tld_length
+
+ env = @env.merge(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestUrlFor < BaseRequestTest
test "url_for class method" do
e = assert_raise(ArgumentError) { url_for(:host => nil) }
assert_match(/Please provide the :host parameter/, e.message)
@@ -31,7 +53,9 @@ class RequestTest < ActiveSupport::TestCase
assert_equal 'http://www.example.com?params=', url_for(:params => '')
assert_equal 'http://www.example.com?params=1', url_for(:params => 1)
end
+end
+class RequestIP < BaseRequestTest
test "remote ip" do
request = stub_request 'REMOTE_ADDR' => '1.2.3.4'
assert_equal '1.2.3.4', request.remote_ip
@@ -220,10 +244,12 @@ class RequestTest < ActiveSupport::TestCase
end
test "remote ip middleware not present still returns an IP" do
- request = ActionDispatch::Request.new({'REMOTE_ADDR' => '127.0.0.1'})
+ request = stub_request('REMOTE_ADDR' => '127.0.0.1')
assert_equal '127.0.0.1', request.remote_ip
end
+end
+class RequestDomain < BaseRequestTest
test "domains" do
request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
assert_equal "rubyonrails.org", request.domain
@@ -281,7 +307,9 @@ class RequestTest < ActiveSupport::TestCase
assert_equal [], request.subdomains
assert_equal "", request.subdomain
end
+end
+class RequestPort < BaseRequestTest
test "standard_port" do
request = stub_request
assert_equal 80, request.standard_port
@@ -323,7 +351,9 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
assert_equal ':8080', request.port_string
end
+end
+class RequestPath < BaseRequestTest
test "full path" do
request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1'
assert_equal "/path/of/some/uri?mapped=1", request.fullpath
@@ -354,6 +384,32 @@ class RequestTest < ActiveSupport::TestCase
assert_equal "/of/some/uri", request.path_info
end
+ test "original_fullpath returns ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+
+ test "original_url returns url built using ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar",
+ 'HTTP_HOST' => "example.org",
+ 'rack.url_scheme' => "http")
+
+ url = request.original_url
+ assert_equal "http://example.org/foo?bar", url
+ end
+
+ test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do
+ request = stub_request('PATH_INFO' => "/foo",
+ 'QUERY_STRING' => "bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+end
+
+class RequestHost < BaseRequestTest
test "host with default port" do
request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
assert_equal "rubyonrails.org", request.host_with_port
@@ -364,15 +420,174 @@ class RequestTest < ActiveSupport::TestCase
assert_equal "rubyonrails.org:81", request.host_with_port
end
- test "server software" do
- request = stub_request
- assert_equal nil, request.server_software
+ test "proxy request" do
+ request = stub_request 'HTTP_HOST' => 'glu.ttono.us:80'
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "http host" do
+ request = stub_request 'HTTP_HOST' => "rubyonrails.org:8080"
+ assert_equal "rubyonrails.org", request.host
+ assert_equal "rubyonrails.org:8080", request.host_with_port
+
+ request = stub_request 'HTTP_X_FORWARDED_HOST' => "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", request.host
+ end
+
+ test "http host with default port overrides server port" do
+ request = stub_request 'HTTP_HOST' => "rubyonrails.org"
+ assert_equal "rubyonrails.org", request.host_with_port
+ end
+
+ test "host with port if http standard port is specified" do
+ request = stub_request 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:80"
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "host with port if https standard port is specified" do
+ request = stub_request(
+ 'HTTP_X_FORWARDED_PROTO' => "https",
+ 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:443"
+ )
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "host if ipv6 reference" do
+ request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host
+ end
+
+ test "host if ipv6 reference with port" do
+ request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]:8008"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host
+ end
+end
+
+class RequestCGI < BaseRequestTest
+ test "CGI environment variables" do
+ request = stub_request(
+ "AUTH_TYPE" => "Basic",
+ "GATEWAY_INTERFACE" => "CGI/1.1",
+ "HTTP_ACCEPT" => "*/*",
+ "HTTP_ACCEPT_CHARSET" => "UTF-8",
+ "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
+ "HTTP_ACCEPT_LANGUAGE" => "en",
+ "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
+ "HTTP_FROM" => "googlebot",
+ "HTTP_HOST" => "glu.ttono.us:8007",
+ "HTTP_NEGOTIATE" => "trans",
+ "HTTP_PRAGMA" => "no-cache",
+ "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
+ "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
+ "PATH_INFO" => "/homepage/",
+ "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
+ "QUERY_STRING" => "",
+ "REMOTE_ADDR" => "207.7.108.53",
+ "REMOTE_HOST" => "google.com",
+ "REMOTE_IDENT" => "kevin",
+ "REMOTE_USER" => "kevin",
+ "REQUEST_METHOD" => "GET",
+ "SCRIPT_NAME" => "/dispatch.fcgi",
+ "SERVER_NAME" => "glu.ttono.us",
+ "SERVER_PORT" => "8007",
+ "SERVER_PROTOCOL" => "HTTP/1.1",
+ "SERVER_SOFTWARE" => "lighttpd/1.4.5",
+ )
+
+ assert_equal "Basic", request.auth_type
+ assert_equal 0, request.content_length
+ assert_equal nil, request.content_mime_type
+ assert_equal "CGI/1.1", request.gateway_interface
+ assert_equal "*/*", request.accept
+ assert_equal "UTF-8", request.accept_charset
+ assert_equal "gzip, deflate", request.accept_encoding
+ assert_equal "en", request.accept_language
+ assert_equal "no-cache, max-age=0", request.cache_control
+ assert_equal "googlebot", request.from
+ assert_equal "glu.ttono.us", request.host
+ assert_equal "trans", request.negotiate
+ assert_equal "no-cache", request.pragma
+ assert_equal "http://www.google.com/search?q=glu.ttono.us", request.referer
+ assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", request.user_agent
+ assert_equal "/homepage/", request.path_info
+ assert_equal "/home/kevinc/sites/typo/public/homepage/", request.path_translated
+ assert_equal "", request.query_string
+ assert_equal "207.7.108.53", request.remote_addr
+ assert_equal "google.com", request.remote_host
+ assert_equal "kevin", request.remote_ident
+ assert_equal "kevin", request.remote_user
+ assert_equal "GET", request.request_method
+ assert_equal "/dispatch.fcgi", request.script_name
+ assert_equal "glu.ttono.us", request.server_name
+ assert_equal 8007, request.server_port
+ assert_equal "HTTP/1.1", request.server_protocol
+ assert_equal "lighttpd", request.server_software
+ end
+end
+
+class RequestCookie < BaseRequestTest
+ test "cookie syntax resilience" do
+ request = stub_request("HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes")
+ assert_equal "c84ace84796670c052c6ceb2451fb0f2", request.cookies["_session_id"], request.cookies.inspect
+ assert_equal "yes", request.cookies["is_admin"], request.cookies.inspect
+
+ # some Nokia phone browsers omit the space after the semicolon separator.
+ # some developers have grown accustomed to using comma in cookie values.
+ request = stub_request("HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes")
+ assert_equal "c84ace847", request.cookies["_session_id"], request.cookies.inspect
+ assert_equal "yes", request.cookies["is_admin"], request.cookies.inspect
+ end
+end
- request = stub_request 'SERVER_SOFTWARE' => 'Apache3.422'
- assert_equal 'apache', request.server_software
+class RequestParamsParsing < BaseRequestTest
+ test "doesnt break when content type has charset" do
+ request = stub_request(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_LENGTH' => "flamenco=love".length,
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8',
+ 'rack.input' => StringIO.new("flamenco=love")
+ )
- request = stub_request 'SERVER_SOFTWARE' => 'lighttpd(1.1.4)'
- assert_equal 'lighttpd', request.server_software
+ assert_equal({"flamenco"=> "love"}, request.request_parameters)
+ end
+
+ test "doesnt interpret request uri as query string when missing" do
+ request = stub_request('REQUEST_URI' => 'foo')
+ assert_equal({}, request.query_parameters)
+ end
+end
+
+class RequestRewind < BaseRequestTest
+ test "body should be rewound" do
+ data = 'rewind'
+ env = {
+ 'rack.input' => StringIO.new(data),
+ 'CONTENT_LENGTH' => data.length,
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8'
+ }
+
+ # Read the request body by parsing params.
+ request = stub_request(env)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+
+ test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do
+ request = stub_request(
+ 'rack.input' => StringIO.new("raw"),
+ 'CONTENT_LENGTH' => 3
+ )
+ assert_equal "raw", request.raw_post
+ assert_equal "raw", request.env['rack.input'].read
+ end
+end
+
+class RequestProtocol < BaseRequestTest
+ test "server software" do
+ assert_equal 'lighttpd', stub_request('SERVER_SOFTWARE' => 'lighttpd/1.4.5').server_software
+ assert_equal 'apache', stub_request('SERVER_SOFTWARE' => 'Apache3.422').server_software
end
test "xml http request" do
@@ -391,19 +606,12 @@ class RequestTest < ActiveSupport::TestCase
end
test "reports ssl" do
- request = stub_request
- assert !request.ssl?
-
- request = stub_request 'HTTPS' => 'on'
- assert request.ssl?
+ assert !stub_request.ssl?
+ assert stub_request('HTTPS' => 'on').ssl?
end
test "reports ssl when proxied via lighttpd" do
- request = stub_request
- assert !request.ssl?
-
- request = stub_request 'HTTP_X_FORWARDED_PROTO' => 'https'
- assert request.ssl?
+ assert stub_request('HTTP_X_FORWARDED_PROTO' => 'https').ssl?
end
test "scheme returns https when proxied" do
@@ -411,63 +619,72 @@ class RequestTest < ActiveSupport::TestCase
assert !request.ssl?
assert_equal 'http', request.scheme
- request = stub_request 'rack.url_scheme' => 'http', 'HTTP_X_FORWARDED_PROTO' => 'https'
+ request = stub_request(
+ 'rack.url_scheme' => 'http',
+ 'HTTP_X_FORWARDED_PROTO' => 'https'
+ )
assert request.ssl?
assert_equal 'https', request.scheme
end
+end
- test "String request methods" do
- [:get, :post, :patch, :put, :delete].each do |method|
- request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
- assert_equal method.to_s.upcase, request.method
- end
- end
+class RequestMethod < BaseRequestTest
+ test "request methods" do
+ [:post, :get, :patch, :put, :delete].each do |method|
+ request = stub_request('REQUEST_METHOD' => method.to_s.upcase)
- test "Symbol forms of request methods via method_symbol" do
- [:get, :post, :patch, :put, :delete].each do |method|
- request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
+ assert_equal method.to_s.upcase, request.method
assert_equal method, request.method_symbol
end
end
test "invalid http method raises exception" do
assert_raise(ActionController::UnknownHttpMethod) do
- request = stub_request 'REQUEST_METHOD' => 'RANDOM_METHOD'
- request.request_method
+ stub_request('REQUEST_METHOD' => 'RANDOM_METHOD').request_method
end
end
test "allow method hacking on post" do
%w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
- request = stub_request "REQUEST_METHOD" => method.to_s.upcase
+ request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
+
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
end
test "invalid method hacking on post raises exception" do
assert_raise(ActionController::UnknownHttpMethod) do
- request = stub_request "REQUEST_METHOD" => "_RANDOM_METHOD"
- request.request_method
+ stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').request_method
end
end
test "restrict method hacking" do
[:get, :patch, :put, :delete].each do |method|
- request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
- 'action_dispatch.request.request_parameters' => { :_method => 'put' }
+ request = stub_request(
+ 'action_dispatch.request.request_parameters' => { :_method => 'put' },
+ 'REQUEST_METHOD' => method.to_s.upcase
+ )
+
assert_equal method.to_s.upcase, request.method
end
end
test "post masquerading as patch" do
- request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
+ request = stub_request(
+ 'REQUEST_METHOD' => 'PATCH',
+ "rack.methodoverride.original_method" => "POST"
+ )
+
assert_equal "POST", request.method
assert_equal "PATCH", request.request_method
assert request.patch?
end
test "post masquerading as put" do
- request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
+ request = stub_request(
+ 'REQUEST_METHOD' => 'PUT',
+ "rack.methodoverride.original_method" => "POST"
+ )
assert_equal "POST", request.method
assert_equal "PUT", request.request_method
assert request.put?
@@ -493,7 +710,9 @@ class RequestTest < ActiveSupport::TestCase
end
end
end
+end
+class RequestFormat < BaseRequestTest
test "xml format" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => 'xml' })
@@ -513,109 +732,55 @@ class RequestTest < ActiveSupport::TestCase
end
test "XMLHttpRequest" do
- request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
- 'HTTP_ACCEPT' =>
- [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(",")
+ request = stub_request(
+ 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
+ 'HTTP_ACCEPT' => [Mime::JS, Mime::HTML, Mime::XML, "text/xml", Mime::ALL].join(",")
+ )
request.expects(:parameters).at_least_once.returns({})
assert request.xhr?
assert_equal Mime::JS, request.format
end
- test "content type" do
- request = stub_request 'CONTENT_TYPE' => 'text/html'
- assert_equal Mime::HTML, request.content_mime_type
- end
-
- test "can override format with parameter" do
+ test "can override format with parameter negative" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :txt })
assert !request.format.xml?
+ end
+ test "can override format with parameter positive" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :xml })
assert request.format.xml?
end
- test "no content type" do
- request = stub_request
- assert_equal nil, request.content_mime_type
- end
-
- test "content type is XML" do
- request = stub_request 'CONTENT_TYPE' => 'application/xml'
- assert_equal Mime::XML, request.content_mime_type
- end
-
- test "content type with charset" do
- request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8'
- assert_equal Mime::XML, request.content_mime_type
- end
-
- test "user agent" do
- request = stub_request 'HTTP_USER_AGENT' => 'TestAgent'
- assert_equal 'TestAgent', request.user_agent
- end
-
- test "parameters" do
- request = stub_request
- request.stubs(:request_parameters).returns({ "foo" => 1 })
- request.stubs(:query_parameters).returns({ "bar" => 2 })
-
- assert_equal({"foo" => 1, "bar" => 2}, request.parameters)
- assert_equal({"foo" => 1}, request.request_parameters)
- assert_equal({"bar" => 2}, request.query_parameters)
- end
-
- test "parameters still accessible after rack parse error" do
- mock_rack_env = { "QUERY_STRING" => "x[y]=1&x[y][][w]=2", "rack.input" => "foo" }
- request = nil
- request = stub_request(mock_rack_env)
-
- assert_raises(ActionController::BadRequest) do
- # rack will raise a TypeError when parsing this query string
- request.parameters
- end
-
- assert_equal({}, request.parameters)
- end
-
- test "we have access to the original exception" do
- mock_rack_env = { "QUERY_STRING" => "x[y]=1&x[y][][w]=2", "rack.input" => "foo" }
- request = nil
- request = stub_request(mock_rack_env)
-
- e = assert_raises(ActionController::BadRequest) do
- # rack will raise a TypeError when parsing this query string
- request.parameters
- end
-
- assert e.original_exception
- assert_equal e.original_exception.backtrace, e.backtrace
- end
-
- test "formats with accept header" do
+ test "formats text/html with accept header" do
request = stub_request 'HTTP_ACCEPT' => 'text/html'
- request.expects(:parameters).at_least_once.returns({})
assert_equal [Mime::HTML], request.formats
+ end
+ test "formats blank with accept header" do
request = stub_request 'HTTP_ACCEPT' => ''
- request.expects(:parameters).at_least_once.returns({})
assert_equal [Mime::HTML], request.formats
+ end
- request = stub_request 'HTTP_ACCEPT' => '',
- 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
- request.expects(:parameters).at_least_once.returns({})
+ test "formats XMLHttpRequest with accept header" do
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
assert_equal [Mime::JS], request.formats
+ end
- request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
- 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
- request.expects(:parameters).at_least_once.returns({})
+ test "formats application/xml with accept header" do
+ request = stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest")
assert_equal [Mime::XML], request.formats
+ end
+ test "formats format:text with accept header" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :txt })
assert_equal [Mime::TEXT], request.formats
+ end
+ test "formats format:unknown with accept header" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :unknown })
assert_instance_of Mime::NullType, request.format
@@ -669,30 +834,87 @@ class RequestTest < ActiveSupport::TestCase
ActionDispatch::Request.ignore_accept_header = false
end
end
+end
- test "negotiate_mime" do
- request = stub_request 'HTTP_ACCEPT' => 'text/html',
- 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+class RequestMimeType < BaseRequestTest
+ test "content type" do
+ assert_equal Mime::HTML, stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
+ end
- request.expects(:parameters).at_least_once.returns({})
+ test "no content type" do
+ assert_equal nil, stub_request.content_mime_type
+ end
+
+ test "content type is XML" do
+ assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
+ end
+
+ test "content type with charset" do
+ assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
+ end
+
+ test "user agent" do
+ assert_equal 'TestAgent', stub_request('HTTP_USER_AGENT' => 'TestAgent').user_agent
+ end
+
+ test "negotiate_mime" do
+ request = stub_request(
+ 'HTTP_ACCEPT' => 'text/html',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ )
assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
+ end
+
+ test "negotiate_mime with content_type" do
+ request = stub_request(
+ 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ )
- request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
- 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
- request.expects(:parameters).at_least_once.returns({})
assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
end
+end
- test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do
- request = stub_request('rack.input' => StringIO.new("foo"),
- 'CONTENT_LENGTH' => 3)
- assert_equal "foo", request.raw_post
- assert_equal "foo", request.env['rack.input'].read
+class RequestParameters < BaseRequestTest
+ test "parameters" do
+ request = stub_request
+ request.expects(:request_parameters).at_least_once.returns({ "foo" => 1 })
+ request.expects(:query_parameters).at_least_once.returns({ "bar" => 2 })
+
+ assert_equal({"foo" => 1, "bar" => 2}, request.parameters)
+ assert_equal({"foo" => 1}, request.request_parameters)
+ assert_equal({"bar" => 2}, request.query_parameters)
end
+ test "parameters still accessible after rack parse error" do
+ request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
+
+ assert_raises(ActionController::BadRequest) do
+ # rack will raise a TypeError when parsing this query string
+ request.parameters
+ end
+
+ assert_equal({}, request.parameters)
+ end
+
+ test "we have access to the original exception" do
+ request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
+
+ e = assert_raises(ActionController::BadRequest) do
+ # rack will raise a TypeError when parsing this query string
+ request.parameters
+ end
+
+ assert e.original_exception
+ assert_equal e.original_exception.backtrace, e.backtrace
+ end
+end
+
+
+class RequestParameterFilter < BaseRequestTest
test "process parameter filter" do
test_hashes = [
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
@@ -721,9 +943,14 @@ class RequestTest < ActiveSupport::TestCase
end
test "filtered_parameters returns params filtered" do
- request = stub_request('action_dispatch.request.parameters' =>
- { 'lifo' => 'Pratik', 'amount' => '420', 'step' => '1' },
- 'action_dispatch.parameter_filter' => [:lifo, :amount])
+ request = stub_request(
+ 'action_dispatch.request.parameters' => {
+ 'lifo' => 'Pratik',
+ 'amount' => '420',
+ 'step' => '1'
+ },
+ 'action_dispatch.parameter_filter' => [:lifo, :amount]
+ )
params = request.filtered_parameters
assert_equal "[FILTERED]", params["lifo"]
@@ -732,10 +959,14 @@ class RequestTest < ActiveSupport::TestCase
end
test "filtered_env filters env as a whole" do
- request = stub_request('action_dispatch.request.parameters' =>
- { 'amount' => '420', 'step' => '1' }, "RAW_POST_DATA" => "yada yada",
- 'action_dispatch.parameter_filter' => [:lifo, :amount])
-
+ request = stub_request(
+ 'action_dispatch.request.parameters' => {
+ 'amount' => '420',
+ 'step' => '1'
+ },
+ "RAW_POST_DATA" => "yada yada",
+ 'action_dispatch.parameter_filter' => [:lifo, :amount]
+ )
request = stub_request(request.filtered_env)
assert_equal "[FILTERED]", request.raw_post
@@ -745,9 +976,11 @@ class RequestTest < ActiveSupport::TestCase
test "filtered_path returns path with filtered query string" do
%w(; &).each do |sep|
- request = stub_request('QUERY_STRING' => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep),
+ request = stub_request(
+ 'QUERY_STRING' => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep),
'PATH_INFO' => '/authenticate',
- 'action_dispatch.parameter_filter' => [:secret, :api_key])
+ 'action_dispatch.parameter_filter' => [:secret, :api_key]
+ )
path = request.filtered_path
assert_equal %w(/authenticate?username=sikachu secret=[FILTERED] api_key=[FILTERED]).join(sep), path
@@ -755,56 +988,40 @@ class RequestTest < ActiveSupport::TestCase
end
test "filtered_path should not unescape a genuine '[FILTERED]' value" do
- request = stub_request('QUERY_STRING' => "secret=bd4f21f&genuine=%5BFILTERED%5D",
+ request = stub_request(
+ 'QUERY_STRING' => "secret=bd4f21f&genuine=%5BFILTERED%5D",
'PATH_INFO' => '/authenticate',
- 'action_dispatch.parameter_filter' => [:secret])
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
path = request.filtered_path
- assert_equal "/authenticate?secret=[FILTERED]&genuine=%5BFILTERED%5D", path
+ assert_equal request.script_name + "/authenticate?secret=[FILTERED]&genuine=%5BFILTERED%5D", path
end
test "filtered_path should preserve duplication of keys in query string" do
- request = stub_request('QUERY_STRING' => "username=sikachu&secret=bd4f21f&username=fxn",
+ request = stub_request(
+ 'QUERY_STRING' => "username=sikachu&secret=bd4f21f&username=fxn",
'PATH_INFO' => '/authenticate',
- 'action_dispatch.parameter_filter' => [:secret])
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
path = request.filtered_path
- assert_equal "/authenticate?username=sikachu&secret=[FILTERED]&username=fxn", path
+ assert_equal request.script_name + "/authenticate?username=sikachu&secret=[FILTERED]&username=fxn", path
end
test "filtered_path should ignore searchparts" do
- request = stub_request('QUERY_STRING' => "secret",
+ request = stub_request(
+ 'QUERY_STRING' => "secret",
'PATH_INFO' => '/authenticate',
- 'action_dispatch.parameter_filter' => [:secret])
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
path = request.filtered_path
- assert_equal "/authenticate?secret", path
- end
-
- test "original_fullpath returns ORIGINAL_FULLPATH" do
- request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar")
-
- path = request.original_fullpath
- assert_equal "/foo?bar", path
- end
-
- test "original_url returns url built using ORIGINAL_FULLPATH" do
- request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar",
- 'HTTP_HOST' => "example.org",
- 'rack.url_scheme' => "http")
-
- url = request.original_url
- assert_equal "http://example.org/foo?bar", url
- end
-
- test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do
- request = stub_request('PATH_INFO' => "/foo",
- 'QUERY_STRING' => "bar")
-
- path = request.original_fullpath
- assert_equal "/foo?bar", path
+ assert_equal request.script_name + "/authenticate?secret", path
end
+end
+class RequestEtag < BaseRequestTest
test "if_none_match_etags none" do
request = stub_request
@@ -843,11 +1060,25 @@ class RequestTest < ActiveSupport::TestCase
assert request.etag_matches?(etag), etag
end
end
+end
+class RequestVarient < BaseRequestTest
test "setting variant" do
request = stub_request
+
request.variant = :mobile
- assert_equal :mobile, request.variant
+ assert_equal [:mobile], request.variant
+
+ request.variant = [:phone, :tablet]
+ assert_equal [:phone, :tablet], request.variant
+
+ assert_raise ArgumentError do
+ request.variant = [:phone, "tablet"]
+ end
+
+ assert_raise ArgumentError do
+ request.variant = "yolo"
+ end
end
test "setting variant with non symbol value" do
@@ -856,16 +1087,4 @@ class RequestTest < ActiveSupport::TestCase
request.variant = "mobile"
end
end
-
-protected
-
- def stub_request(env = {})
- ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true
- @trusted_proxies ||= nil
- ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies)
- tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1
- ip_app.call(env)
- ActionDispatch::Http::URL.tld_length = tld_length
- ActionDispatch::Request.new(env)
- end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 4501ea095c..959a3bc5cd 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -217,6 +217,24 @@ class ResponseTest < ActiveSupport::TestCase
assert_not @response.respond_to?(:method_missing)
assert @response.respond_to?(:method_missing, true)
end
+
+ test "can be destructured into status, headers and an enumerable body" do
+ response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
+ status, headers, body = response
+
+ assert_equal 404, status
+ assert_equal({ 'Content-Type' => 'text/plain' }, headers)
+ assert_equal ['Not Found'], body.each.to_a
+ end
+
+ test "[response].flatten does not recurse infinitely" do
+ Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
+ status, headers, body = [@response].flatten
+ assert_equal @response.status, status
+ assert_equal @response.headers, headers
+ assert_equal @response.body, body.each.to_a.join
+ end
+ 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 18a52f13a7..ff33dd5652 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -54,6 +54,27 @@ module ActionDispatch
], output
end
+ def test_displaying_routes_for_engines_without_routes
+ engine = Class.new(Rails::Engine) do
+ def self.inspect
+ "Blog::Engine"
+ end
+ end
+ engine.routes.draw do
+ end
+
+ output = draw do
+ mount engine => "/blog", as: "blog"
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " blog /blog Blog::Engine",
+ "",
+ "Routes for Blog::Engine:"
+ ], output
+ end
+
def test_cart_inspect
output = draw do
get '/cart', :to => 'cart#show'
@@ -160,6 +181,29 @@ module ActionDispatch
], output
end
+ def test_rake_routes_shows_routes_with_dashes
+ output = draw do
+ get 'about-us' => 'pages#about_us'
+ get 'our-work/latest'
+
+ resources :photos, only: [:show] do
+ get 'user-favorites', on: :collection
+ get 'preview-photo', on: :member
+ get 'summary-text'
+ end
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ " about_us GET /about-us(.:format) pages#about_us",
+ " our_work_latest GET /our-work/latest(.:format) our_work#latest",
+ "user_favorites_photos GET /photos/user-favorites(.:format) photos#user_favorites",
+ " preview_photo_photo GET /photos/:id/preview-photo(.:format) photos#preview_photo",
+ " photo_summary_text GET /photos/:photo_id/summary-text(.:format) photos#summary_text",
+ " photo GET /photos/:id(.:format) photos#show"
+ ], output
+ end
+
class RackApp
def self.call(env)
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5a532dc38f..b22a56bb27 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1031,6 +1031,136 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'users/home#index', @response.body
end
+ def test_namespaced_shallow_routes_with_module_option
+ draw do
+ namespace :foo, module: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'bar/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'bar/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'bar/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', foo_comment_path('2')
+ assert_equal 'bar/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_path_option
+ draw do
+ namespace :foo, path: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/bar/posts'
+ assert_equal '/bar/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/bar/posts/1'
+ assert_equal '/bar/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/bar/posts/1/comments'
+ assert_equal '/bar/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/bar/comments/2'
+ assert_equal '/bar/comments/2', foo_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_as_option
+ draw do
+ namespace :foo, as: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', bar_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', bar_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', bar_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', bar_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_shallow_path_option
+ draw do
+ namespace :foo, shallow_path: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/bar/comments/2'
+ assert_equal '/bar/comments/2', foo_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_shallow_prefix_option
+ draw do
+ namespace :foo, shallow_prefix: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', bar_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
def test_namespace_containing_numbers
draw do
namespace :v2 do
@@ -1828,6 +1958,60 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
end
+ def test_shallow_nested_resources_inside_resource
+ draw do
+ resource :membership, shallow: true do
+ resources :cards
+ end
+ end
+
+ get '/membership/cards'
+ assert_equal 'cards#index', @response.body
+ assert_equal '/membership/cards', membership_cards_path
+
+ get '/membership/cards/new'
+ assert_equal 'cards#new', @response.body
+ assert_equal '/membership/cards/new', new_membership_card_path
+
+ post '/membership/cards'
+ assert_equal 'cards#create', @response.body
+
+ get '/cards/1'
+ assert_equal 'cards#show', @response.body
+ assert_equal '/cards/1', card_path('1')
+
+ get '/cards/1/edit'
+ assert_equal 'cards#edit', @response.body
+ assert_equal '/cards/1/edit', edit_card_path('1')
+
+ put '/cards/1'
+ assert_equal 'cards#update', @response.body
+
+ patch '/cards/1'
+ assert_equal 'cards#update', @response.body
+
+ delete '/cards/1'
+ assert_equal 'cards#destroy', @response.body
+ end
+
+ def test_shallow_deeply_nested_resources
+ draw do
+ resources :blogs do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ get '/comments/1'
+ assert_equal 'comments#show', @response.body
+
+ assert_equal '/comments/1', comment_path('1')
+ assert_equal '/blogs/new', new_blog_path
+ assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1)
+ assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2)
+ end
+
def test_shallow_nested_resources_within_scope
draw do
scope '/hello' do
@@ -1889,6 +2073,65 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'notes#destroy', @response.body
end
+ def test_shallow_option_nested_resources_within_scope
+ draw do
+ scope '/hello' do
+ resources :notes, :shallow => true do
+ resources :trackbacks
+ end
+ end
+ end
+
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
+
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
+
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
+
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
+
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
+
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
+
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
+
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
+
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
+ end
+
def test_custom_resource_routes_are_scoped
draw do
resources :customers do
@@ -2894,6 +3137,194 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/foo', foo_root_path
end
+ def test_trailing_slash
+ draw do
+ resources :streams
+ end
+
+ get '/streams'
+ assert @response.ok?, 'route without trailing slash should work'
+
+ get '/streams/'
+ assert @response.ok?, 'route with trailing slash should work'
+
+ get '/streams?foobar'
+ assert @response.ok?, 'route without trailing slash and with QUERY_STRING should work'
+
+ get '/streams/?foobar'
+ assert @response.ok?, 'route with trailing slash and with QUERY_STRING should work'
+ end
+
+ def test_route_with_dashes_in_path
+ draw do
+ get '/contact-us', to: 'pages#contact_us'
+ end
+
+ get '/contact-us'
+ assert_equal 'pages#contact_us', @response.body
+ assert_equal '/contact-us', contact_us_path
+ end
+
+ def test_shorthand_route_with_dashes_in_path
+ draw do
+ get '/about-us/index'
+ end
+
+ get '/about-us/index'
+ assert_equal 'about_us#index', @response.body
+ assert_equal '/about-us/index', about_us_index_path
+ end
+
+ def test_resource_routes_with_dashes_in_path
+ draw do
+ resources :photos, only: [:show] do
+ get 'user-favorites', on: :collection
+ get 'preview-photo', on: :member
+ get 'summary-text'
+ end
+ end
+
+ get '/photos/user-favorites'
+ assert_equal 'photos#user_favorites', @response.body
+ assert_equal '/photos/user-favorites', user_favorites_photos_path
+
+ get '/photos/1/preview-photo'
+ assert_equal 'photos#preview_photo', @response.body
+ assert_equal '/photos/1/preview-photo', preview_photo_photo_path('1')
+
+ get '/photos/1/summary-text'
+ assert_equal 'photos#summary_text', @response.body
+ assert_equal '/photos/1/summary-text', photo_summary_text_path('1')
+
+ get '/photos/1'
+ assert_equal 'photos#show', @response.body
+ assert_equal '/photos/1', photo_path('1')
+ end
+
+ def test_shallow_path_inside_namespace_is_not_added_twice
+ draw do
+ namespace :admin do
+ shallow do
+ resources :posts do
+ resources :comments
+ end
+ end
+ end
+ end
+
+ get '/admin/posts/1/comments'
+ assert_equal 'admin/comments#index', @response.body
+ assert_equal '/admin/posts/1/comments', admin_post_comments_path('1')
+ end
+
+ def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
+ draw do
+ scope shallow_path: 'projects', shallow_prefix: 'project' do
+ resources :projects do
+ resources :files, controller: 'project_files', shallow: true
+ end
+ end
+ end
+
+ get '/projects'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/projects', projects_path
+
+ get '/projects/new'
+ assert_equal 'projects#new', @response.body
+ assert_equal '/projects/new', new_project_path
+
+ post '/projects'
+ assert_equal 'projects#create', @response.body
+
+ get '/projects/1'
+ assert_equal 'projects#show', @response.body
+ assert_equal '/projects/1', project_path('1')
+
+ get '/projects/1/edit'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path('1')
+
+ patch '/projects/1'
+ assert_equal 'projects#update', @response.body
+
+ delete '/projects/1'
+ assert_equal 'projects#destroy', @response.body
+
+ get '/projects/1/files'
+ assert_equal 'project_files#index', @response.body
+ assert_equal '/projects/1/files', project_files_path('1')
+
+ get '/projects/1/files/new'
+ assert_equal 'project_files#new', @response.body
+ assert_equal '/projects/1/files/new', new_project_file_path('1')
+
+ post '/projects/1/files'
+ assert_equal 'project_files#create', @response.body
+
+ get '/projects/files/2'
+ assert_equal 'project_files#show', @response.body
+ assert_equal '/projects/files/2', project_file_path('2')
+
+ get '/projects/files/2/edit'
+ assert_equal 'project_files#edit', @response.body
+ assert_equal '/projects/files/2/edit', edit_project_file_path('2')
+
+ patch '/projects/files/2'
+ assert_equal 'project_files#update', @response.body
+
+ delete '/projects/files/2'
+ assert_equal 'project_files#destroy', @response.body
+ end
+
+ def test_scope_path_is_copied_to_shallow_path
+ draw do
+ scope path: 'foo' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/foo/comments/1', comment_path('1')
+ end
+
+ def test_scope_as_is_copied_to_shallow_prefix
+ draw do
+ scope as: 'foo' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/comments/1', foo_comment_path('1')
+ end
+
+ def test_scope_shallow_prefix_is_not_overwritten_by_as
+ draw do
+ scope as: 'foo', shallow_prefix: 'bar' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/comments/1', bar_comment_path('1')
+ end
+
+ def test_scope_shallow_path_is_not_overwritten_by_path
+ draw do
+ scope path: 'foo', shallow_path: 'bar' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/bar/comments/1', comment_path('1')
+ end
+
private
def draw(&block)
@@ -3165,8 +3596,8 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
include Routes.url_helpers
def app; Routes end
- test 'escapes generated path segment' do
- assert_equal '/a%20b/c+d', segment_path(:segment => 'a b/c+d')
+ test 'escapes slash in generated path segment' do
+ assert_equal '/a%20b%2Fc+d', segment_path(:segment => 'a b/c+d')
end
test 'unescapes recognized path segment' do
@@ -3174,7 +3605,7 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
assert_equal 'a b/c+d', @response.body
end
- test 'escapes generated path splat' do
+ test 'does not escape slash in generated path splat' do
assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d')
end
@@ -3359,6 +3790,8 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
get '/post(/:action(/:id))' => ok, as: :posts
get '/:foo/:foo_type/bars/:id' => ok, as: :bar
get '/projects/:id.:format' => ok, as: :project
+ get '/pages/:id' => ok, as: :page
+ get '/wiki/*page' => ok, as: :wiki
end
end
@@ -3391,6 +3824,26 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json)
assert_equal '/projects/1.json', project_path(1, :json)
end
+
+ test 'segments with question marks are escaped' do
+ assert_equal '/pages/foo%3Fbar', Routes.url_helpers.page_path('foo?bar')
+ assert_equal '/pages/foo%3Fbar', page_path('foo?bar')
+ end
+
+ test 'segments with slashes are escaped' do
+ assert_equal '/pages/foo%2Fbar', Routes.url_helpers.page_path('foo/bar')
+ assert_equal '/pages/foo%2Fbar', page_path('foo/bar')
+ end
+
+ test 'glob segments with question marks are escaped' do
+ assert_equal '/wiki/foo%3Fbar', Routes.url_helpers.wiki_path('foo?bar')
+ assert_equal '/wiki/foo%3Fbar', wiki_path('foo?bar')
+ end
+
+ test 'glob segments with slashes are not escaped' do
+ assert_equal '/wiki/foo/bar', Routes.url_helpers.wiki_path('foo/bar')
+ assert_equal '/wiki/foo/bar', wiki_path('foo/bar')
+ end
end
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index e53ce4195b..92544230b2 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'securerandom'
# You need to start a memcached server inorder to run these tests
class MemCacheStoreTest < ActionDispatch::IntegrationTest
@@ -172,7 +173,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
end
@app = self.class.build_app(set) do |middleware|
- middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id'
+ middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}"
middleware.delete "ActionDispatch::ShowExceptions"
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 94969f795a..c3598c5e8e 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -196,6 +196,13 @@ class SSLTest < ActionDispatch::IntegrationTest
response.headers['Location']
end
+ def test_redirect_to_host_with_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:443/path?key=value",
+ response.headers['Location']
+ end
+
def test_redirect_to_secure_host_when_on_subdomain
self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
get "http://ssl.example.org/path?key=value"
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 5bd1806b21..afdda70748 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -136,10 +136,15 @@ module StaticTests
def with_static_file(file)
path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
- File.open(path, "wb+") { |f| f.write(file) }
+ begin
+ File.open(path, "wb+") { |f| f.write(file) }
+ rescue Errno::EPROTO
+ skip "Couldn't create a file #{path}"
+ end
+
yield file
ensure
- File.delete(path)
+ File.delete(path) if File.exist? path
end
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 72f3d1db0d..9f6381f118 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -33,6 +33,12 @@ module ActionDispatch
assert_equal 'foo', uf.tempfile
end
+ def test_to_io_returns_the_tempfile
+ tf = Object.new
+ uf = Http::UploadedFile.new(:tempfile => tf)
+ assert_equal tf, uf.to_io
+ end
+
def test_delegates_path_to_tempfile
tf = Class.new { def path; 'thunderhorse' end }
uf = Http::UploadedFile.new(:tempfile => tf.new)
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
new file mode 100644
index 0000000000..e523b74ae3
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache do %><p>PHONE</p><% end %>
+</body>
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index 93348f4647..584fd56a5c 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -5,11 +5,15 @@ module ActionDispatch
class Router
class TestUtils < ActiveSupport::TestCase
def test_path_escape
- assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d")
+ assert_equal "a/b%20c+d%25", Utils.escape_path("a/b c+d%")
+ end
+
+ def test_segment_escape
+ assert_equal "a%2Fb%20c+d%25", Utils.escape_segment("a/b c+d%")
end
def test_fragment_escape
- assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e")
+ assert_equal "a/b%20c+d%25?e", Utils.escape_fragment("a/b c+d%?e")
end
def test_uri_unescape
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index a286f77633..e54b64e0f3 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -367,7 +367,18 @@ module ActionDispatch
nil, { :controller => "tasks",
:action => "a/b c+d",
}, {})
- assert_equal '/tasks/a/b%20c+d', path
+ assert_equal '/tasks/a%2Fb%20c+d', path
+ end
+
+ def test_generate_escapes_with_namespaced_controller
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ path, _ = @formatter.generate(:path_info,
+ nil, { :controller => "admin/tasks",
+ :action => "a/b c+d",
+ }, {})
+ assert_equal '/admin/tasks/a%2Fb%20c+d', path
end
def test_generate_extra_params
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index a57da72d8c..50ca64d536 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,288 +1,61 @@
-* Use `display:none` instead of `display:inline` for hidden fields
-
- Fixes #6403
-
- *Gaelian Ditchburn*
-
-* The `video_tag` helper accepts a number as `:size`
-
- The `:size` option of the `video_tag` helper now can be specified
- with a stringified number. The `width` and `height` attributes of
- the generated tag will be the same.
-
- *Kuldeep Aggarwal*
-
-* A Cycle object should accept an array and cycle through it as it would with a set of
- comma-separated objects.
-
- arr = [1,2,3]
- cycle(arr) # => '1'
- cycle(arr) # => '2'
- cycle(arr) # => '3'
-
- Previously, it would return the array as a string, because it took the array as a
- single object:
-
- arr = [1,2,3]
- cycle(arr) # => '[1,2,3]'
- cycle(arr) # => '[1,2,3]'
- cycle(arr) # => '[1,2,3]'
-
- *Kristian Freeman*
-
-* Label tags generated by collection helpers only inherit the `:index` and
- `:namespace` from the input, because only these attributes modifies the
- `for` attribute of the label. Also, the input attributes don't have
- precedence over the label attributes anymore.
+* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to
+ `image/x-icon`.
Before:
-
- collection = [[1, true, { class: 'foo' }]]
- f.collection_check_boxes :options, collection, :second, :first do |b|
- b.label(class: 'my_custom_class')
- end
-
- # => <label class="foo" for="user_active_true">1</label>
+
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
After:
- collection = [[1, true, { class: 'foo' }]]
- f.collection_check_boxes :options, collection, :second, :first do |b|
- b.label(class: 'my_custom_class')
- end
-
- # => <label class="my_custom_class" for="user_active_true">1</label>
-
- *Andriel Nuernberg*
-
-* Fixed a long-standing bug in `json_escape` that causes quotation marks to be stripped.
- This method also escapes the \u2028 and \u2029 unicode newline characters which are
- treated as \n in JavaScript. This matches the behaviour of the AS::JSON encoder. (The
- original change in the encoder was introduced in #10534.)
-
- *Godfrey Chan*
-
-* `ActionView::MissingTemplate` includes underscore when raised for a partial.
-
- Fixes #13002.
-
- *Yves Senn*
-
-* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions
-
- *Shimpei Makimoto*
-
-* Fix `simple_format` escapes own output when passing `sanitize: true`
-
- *Paul Seidemann*
-
-* Ensure `ActionView::Digestor.cache` is correctly cleaned up when
- combining recursive templates with `ActionView::Resolver.caching = false`.
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
- *wyaeld*
+ *Geoffroy Lorieux*
-* Fix `collection_check_boxes` generated hidden input to use the name attribute provided
- in the options hash.
+* Remove wrapping div with inline styles for hidden form fields.
- *Angel N. Sciortino*
+ We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
+ inside a form are valid HTML5, and the absense of inline styles help in validating
+ for Content Security Policy.
-* Fix some edge cases for AV `select` helper with `:selected` option.
+ *Joost Baaij*
- *Bogdan Gusiev*
+* `collection_check_boxes` respects `:index` option for the hidden filed name.
-* Ability to pass block to `select` helper
-
- <%= select(report, "campaign_ids") do %>
- <% available_campaigns.each do |c| -%>
- <%= content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json }) %>
- <% end -%>
- <% end -%>
-
- *Bogdan Gusiev*
-
-* Handle `:namespace` form option in collection labels.
+ Fixes #14147.
*Vasiliy Ermolovich*
-* Fix `form_for` when both `namespace` and `as` options are present.
-
- `as` option no longer overwrites `namespace` option when generating
- html id attribute of the form element.
-
- *Adam Niedzielski*
-
-* Fix `excerpt` when `:separator` is `nil`.
-
- *Paul Nikitochkin*
-
-* Only cache template digests if `config.cache_template_loading` is true.
-
- *Josh Lauer*, *Justin Ridgewell*
-
-* Fixed a bug where the lookup details were not being taken into account
- when caching the digest of a template - changes to the details now
- cause a different cache key to be used.
-
- *Daniel Schierbeck*
-
-* Added an `extname` hash option for `javascript_include_tag` method.
-
- Before:
-
- javascript_include_tag('templates.jst')
- # => <script src="/javascripts/templates.jst.js"></script>
-
- After:
-
- javascript_include_tag('templates.jst', extname: false )
- # => <script src="/javascripts/templates.jst"></script>
-
- *Nathan Stitt*
-
-* Fix `current_page?` when the URL contains escaped characters and the
- original URL is using the hexadecimal lowercased.
+* `date_select` helper with option `with_css_classes: true` does not overwrite other classes.
- *Rafael Mendonça França*
-
-* Fix `text_area` to behave like `text_field` when `nil` is given as
- value.
-
- Before:
-
- f.text_field :field, value: nil #=> <input value="">
- f.text_area :field, value: nil #=> <textarea>value of field</textarea>
-
- After:
+ *Izumi Wong-Horiuchi*
- f.text_area :field, value: nil #=> <textarea></textarea>
+* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY`
+ as input.
- *Joel Cogen*
+ Fixes #14405.
-* Element of the `grouped_options_for_select` can
- optionally contain html attributes as the last element of the array.
+ *Yves Senn*
- grouped_options_for_select(
- [["North America", [['United States','US'],"Canada"], data: { foo: 'bar' }]]
- )
+* Add `include_hidden` option to `collection_check_boxes` helper.
*Vasiliy Ermolovich*
-* Fix default rendered format problem when calling `render` without :content_type option.
- It should return :html. Fix #11393.
-
- *Gleb Mazovetskiy* *Oleg* *kennyj*
-
-* Fix `link_to` with block and url hashes.
-
- Before:
-
- link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') }
- # => "<a action=\"bar\" controller=\"foo\"><span>Example site</span></a>"
-
- After:
-
- link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') }
- # => "<a href=\"/foo/bar\"><span>Example site</span></a>"
-
- *Murahashi Sanemat Kenichi*
-
-* Fix "Stack Level Too Deep" error when redering recursive partials.
-
- Fixes #11340.
-
- *Rafael Mendonça França*
-
-* Added an `enforce_utf8` hash option for `form_tag` method.
-
- Control to output a hidden input tag with name `utf8` without monkey
- patching.
-
- Before:
-
- form_tag
- # => '<form>..<input name="utf8" type="hidden" value="&#x2713;" />..</form>'
-
- After:
-
- form_tag
- # => '<form>..<input name="utf8" type="hidden" value="&#x2713;" />..</form>'
-
- form_tag({}, { :enforce_utf8 => false })
- # => '<form>....</form>'
-
- *ma2gedev*
-
-* Remove the deprecated `include_seconds` argument from `distance_of_time_in_words`,
- pass in an `:include_seconds` hash option to use this feature.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated block passing to `FormBuilder#new`.
-
- *Vipul A M*
-
-* Pick `DateField` `DateTimeField` and `ColorField` values from stringified options allowing use of symbol keys with helpers.
-
- *Jon Rowe*
-
-* Remove the deprecated `prompt` argument from `grouped_options_for_select`,
- pass in a `:prompt` hash option to use this feature.
-
- *kennyj*
-
-* Always escape the result of `link_to_unless` method.
-
- Before:
-
- link_to_unless(true, '<b>Showing</b>', 'github.com')
- # => "<b>Showing</b>"
-
- After:
-
- link_to_unless(true, '<b>Showing</b>', 'github.com')
- # => "&lt;b&gt;Showing&lt;/b&gt;"
-
- *dtaniwaki*
-
-* Use a case insensitive URI Regexp for #asset_path.
-
- This fix a problem where the same asset path using different case are generating
- different URIs.
-
- Before:
-
- image_tag("HTTP://google.com")
- # => "<img alt=\"Google\" src=\"/assets/HTTP://google.com\" />"
- image_tag("http://google.com")
- # => "<img alt=\"Google\" src=\"http://google.com\" />"
-
- After:
-
- image_tag("HTTP://google.com")
- # => "<img alt=\"Google\" src=\"HTTP://google.com\" />"
- image_tag("http://google.com")
- # => "<img alt=\"Google\" src=\"http://google.com\" />"
-
- *David Celis*
-
-* Element of the `collection_check_boxes` and `collection_radio_buttons` can
- optionally contain html attributes as the last element of the array.
-
- *Vasiliy Ermolovich*
+* Fixed a problem where the default options for the `button_tag` helper is not
+ applied correctly.
-* Update the HTML `BOOLEAN_ATTRIBUTES` in `ActionView::Helpers::TagHelper`
- to conform to the latest HTML 5.1 spec. Add attributes `allowfullscreen`,
- `default`, `inert`, `sortable`, `truespeed`, `typemustmatch`. Fix attribute
- `seamless` (previously misspelled `seemless`).
+ Fixes #14254.
- *Alex Peattie*
+ *Sergey Prikhodko*
-* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies.
+* Take variants into account when calculating template digests in ActionView::Digestor.
- *Bryan Ricker*
+ 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.
-* First release, ActionView extracted from ActionPack
+ *Piotr Chmolowski, Łukasz Strzałkowski*
- *Piotr Sarnacki*, *Łukasz Strzałkowski*
-Please check [4-0-stable (ActionPack's CHANGELOG)](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 5c729345dc..50712e0830 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -28,6 +28,8 @@ require 'action_view/version'
module ActionView
extend ActiveSupport::Autoload
+ ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
+
eager_autoload do
autoload :Base
autoload :Context
@@ -54,7 +56,6 @@ module ActionView
autoload_at "action_view/template/resolver" do
autoload :Resolver
autoload :PathResolver
- autoload :FileSystemResolver
autoload :OptimizedFileSystemResolver
autoload :FallbackFileSystemResolver
end
@@ -81,8 +82,6 @@ module ActionView
autoload :TestCase
- ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
-
def self.eager_load!
super
ActionView::Helpers.eager_load!
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 8eb7072d0c..455ce531ae 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -153,6 +153,10 @@ module ActionView #:nodoc:
# Specify default_formats that can be rendered.
cattr_accessor :default_formats
+ # Specify whether an error should be raised for missing translations
+ cattr_accessor :raise_on_missing_translations
+ @@raise_on_missing_translations = false
+
class_attribute :_routes
class_attribute :logger
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index b2e8334077..0ccf2515c5 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,7 +1,7 @@
require 'thread_safe'
module ActionView
- class DependencyTracker
+ class DependencyTracker # :nodoc:
@trackers = ThreadSafe::Cache.new
def self.find_dependencies(name, template)
@@ -23,24 +23,52 @@ module ActionView
@trackers.delete(handler)
end
- class ERBTracker
+ class ERBTracker # :nodoc:
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
+ # A valid ruby identifier - suitable for class, method and specially variable names
+ IDENTIFIER = /
+ [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
+ [[:word:]]* # followed by optional letters, numbers or underscores
+ /x
+
+ # Any kind of variable name. e.g. @instance, @@class, $global or local.
+ # Possibly following a method call chain
+ VARIABLE_OR_METHOD_CHAIN = /
+ (?:\$|@{1,2})? # optional global, instance or class variable indicator
+ (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
+ (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
+ /x
+
+ # A simple string literal. e.g. "School's out!"
+ STRING = /
+ (?<quote>['"]) # an opening quote
+ (?<static>.*?) # with anything inside, captured as STATIC
+ \k<quote> # and a matching closing quote
+ /x
+
+ # Part of any hash containing the :partial key
+ PARTIAL_HASH_KEY = /
+ (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
+ \s* # followed by optional spaces
+ /x
+
# Matches:
- # render partial: "comments/comment", collection: commentable.comments
- # render "comments/comments"
- # render 'comments/comments'
- # render('comments/comments')
+ # partial: "comments/comment", collection: @all_comments => "comments/comment"
+ # (object: @single_comment, partial: "comments/comment") => "comments/comment"
#
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
- RENDER_DEPENDENCY = /
- render\s* # render, followed by optional whitespace
- \(? # start an optional parenthesis for the render call
- (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture
- ([@a-z"'][@\w\/\."']+) # the template name itself -- 2nd capture
- /x
+ # "comments/comments"
+ # 'comments/comments'
+ # ('comments/comments')
+ #
+ # (@topic) => "topics/topic"
+ # topics => "topics/topic"
+ # (message.topics) => "topics/topic"
+ RENDER_ARGUMENTS = /\A
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
+ (?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
+ /xm
def self.call(name, template)
new(name, template).dependencies
@@ -68,19 +96,33 @@ module ActionView
end
def render_dependencies
- source.scan(RENDER_DEPENDENCY).
- collect(&:second).uniq.
+ render_dependencies = []
+ render_calls = source.split(/\brender\b/).drop(1)
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
- collect { |name| name.sub(/\A@?([a-z_]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }.
+ render_calls.each do |arguments|
+ arguments.scan(RENDER_ARGUMENTS) do
+ add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
+ add_static_dependency(render_dependencies, Regexp.last_match[:static])
+ end
+ end
- # render("headline") => render("message/headline")
- collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }.
+ render_dependencies.uniq
+ end
+
+ def add_dynamic_dependency(dependencies, dependency)
+ if dependency
+ dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
+ end
+ end
- # replace quotes from string renders
- collect { |name| name.gsub(/["']/, "") }
+ def add_static_dependency(dependencies, dependency)
+ if dependency
+ if dependency.include?('/')
+ dependencies << dependency
+ else
+ dependencies << "#{directory}/#{dependency}"
+ end
+ end
end
def explicit_dependencies
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 5570e2a8dc..72d79735ae 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -9,54 +9,61 @@ module ActionView
@@digest_monitor = Monitor.new
class << self
- def digest(name, format, finder, options = {})
- details_key = finder.details_key.hash
- dependencies = Array.wrap(options[:dependencies])
- cache_key = ([name, details_key, format] + dependencies).join('.')
+ # Supported options:
+ #
+ # * <tt>name</tt> - Template name
+ # * <tt>finder</tt> - An instance of ActionView::LookupContext
+ # * <tt>dependencies</tt> - An array of dependent views
+ # * <tt>partial</tt> - Specifies whether the template is a partial
+ def digest(options)
+ options.assert_valid_keys(:name, :finder, :dependencies, :partial)
+
+ cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
# this is a correctly done double-checked locking idiom
# (ThreadSafe::Cache's lookups have volatile semantics)
@@cache[cache_key] || @@digest_monitor.synchronize do
@@cache.fetch(cache_key) do # re-check under lock
- compute_and_store_digest(cache_key, name, format, finder, options)
+ compute_and_store_digest(cache_key, options)
end
end
end
private
- def compute_and_store_digest(cache_key, name, format, finder, options) # called under @@digest_monitor lock
- klass = if options[:partial] || name.include?("/_")
- # Prevent re-entry or else recursive templates will blow the stack.
- # There is no need to worry about other threads seeing the +false+ value,
- # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
- pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
- PartialDigestor
- else
- Digestor
- end
+ def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock
+ klass = if options[:partial] || options[:name].include?("/_")
+ # Prevent re-entry or else recursive templates will blow the stack.
+ # There is no need to worry about other threads seeing the +false+ value,
+ # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
+ pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
+ PartialDigestor
+ else
+ Digestor
+ end
- digest = klass.new(name, format, finder, options).digest
- # Store the actual digest if config.cache_template_loading is true
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
- digest
- ensure
- # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
- @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
- end
+ digest = klass.new(options).digest
+ # Store the actual digest if config.cache_template_loading is true
+ @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
+ digest
+ ensure
+ # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
+ @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
+ end
end
- attr_reader :name, :format, :finder, :options
+ attr_reader :name, :finder, :options
- def initialize(name, format, finder, options={})
- @name, @format, @finder, @options = name, format, finder, options
+ def initialize(options)
+ @name, @finder = options.values_at(:name, :finder)
+ @options = options.except(:name, :finder)
end
def digest
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :info, "Cache digest for #{name}.#{format}: #{digest}"
+ logger.try :info, " Cache digest for #{template.inspect}: #{digest}"
end
rescue ActionView::MissingTemplate
- logger.try :error, "Couldn't find template for digesting: #{name}.#{format}"
+ logger.try :error, " Couldn't find template for digesting: #{name}"
''
end
@@ -68,13 +75,12 @@ module ActionView
def nested_dependencies
dependencies.collect do |dependency|
- dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies
+ dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies
dependencies.any? ? { dependency => dependencies } : dependency
end
end
private
-
def logger
ActionView::Base.logger
end
@@ -88,7 +94,7 @@ module ActionView
end
def template
- @template ||= finder.find(logical_name, [], partial?, formats: [ format ])
+ @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
end
def source
@@ -97,7 +103,7 @@ module ActionView
def dependency_digest
template_digests = dependencies.collect do |template_name|
- Digestor.digest(template_name, format, finder, partial: true)
+ Digestor.digest(name: template_name, finder: finder, partial: true)
end
(template_digests + injected_dependencies).join("-")
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
new file mode 100644
index 0000000000..9266e55c47
--- /dev/null
+++ b/actionview/lib/action_view/gem_version.rb
@@ -0,0 +1,15 @@
+module ActionView
+ # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index aa49f1edc1..824cdaa45e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -142,22 +142,29 @@ module ActionView
)
end
- # Returns a link loading a favicon file. You may specify a different file
- # in the first argument. The helper accepts an additional options hash where
- # you can override "rel" and "type".
+ # Returns a link tag for a favicon managed by the asset pipeline.
#
- # ==== Options
+ # If a page has no link like the one generated by this helper, browsers
+ # ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
+ # request succeeds. If the favicon changes it is hard to get it updated.
#
- # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
- # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
+ # To have better control applications may let the asset pipeline manage
+ # their favicon storing the file under <tt>app/assets/images</tt>, and
+ # using this helper to generate its corresponding link tag.
#
- # ==== Examples
+ # The helper gets the name of the favicon file as first argument, which
+ # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
+ # to override their defaults, "shortcut icon" and "image/x-icon"
+ # respectively:
+ #
+ # favicon_link_tag
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# favicon_link_tag 'myicon.ico'
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
#
- # Mobile Safari looks for a different <link> tag, pointing to an image that
- # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
+ # Mobile Safari looks for a different link tag, pointing to an image that
+ # will be used if you add the page to the home screen of an iOS device.
# The following call would generate such a tag:
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
@@ -165,7 +172,7 @@ module ActionView
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
+ :type => 'image/x-icon',
:href => path_to_image(source)
}.merge!(options.symbolize_keys))
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index af70a4242a..227ad4cdfa 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# Full usage example:
#
# config/routes.rb:
- # Basecamp::Application.routes.draw do
+ # Rails.application.routes.draw do
# resources :posts
# root to: "posts#index"
# end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index b3af1d4da4..4db8930a26 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -11,7 +11,7 @@ module ActionView
# The best way to use this is by doing key-based cache expiration
# on top of a cache store like Memcached that'll automatically
# kick out old entries. For more on key-based expiration, see:
- # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
+ # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works
#
# When using this method, you list the cache dependency as the name of the cache, like so:
#
@@ -165,10 +165,10 @@ module ActionView
def fragment_name_with_digest(name) #:nodoc:
if @virtual_path
- [
- *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
- Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies)
- ]
+ names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name)
+ digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
+
+ [ *names, digest ]
else
name
end
diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb
index eeb0ed94b9..5af92c4ff2 100644
--- a/actionview/lib/action_view/helpers/csrf_helper.rb
+++ b/actionview/lib/action_view/helpers/csrf_helper.rb
@@ -12,8 +12,11 @@ module ActionView
# These are used to generate the dynamic forms that implement non-remote links with
# <tt>:method</tt>.
#
- # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
- # so they do not use these tags.
+ # You don't need to use these tags for regular forms as they generate their own hidden fields.
+ #
+ # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
+ # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically.
+ #
def csrf_meta_tags
if protect_against_forgery?
[
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 3d091c4a00..2efb9612ac 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -19,6 +19,10 @@ module ActionView
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
# of \date[month].
module DateHelper
+ MINUTES_IN_YEAR = 525600
+ MINUTES_IN_QUARTER_YEAR = 131400
+ MINUTES_IN_THREE_QUARTERS_YEAR = 394200
+
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
# Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
@@ -120,11 +124,11 @@ module ActionView
else
minutes_with_offset = distance_in_minutes
end
- remainder = (minutes_with_offset % 525600)
- distance_in_years = (minutes_with_offset.div 525600)
- if remainder < 131400
+ remainder = (minutes_with_offset % MINUTES_IN_YEAR)
+ distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
+ if remainder < MINUTES_IN_QUARTER_YEAR
locale.t(:about_x_years, :count => distance_in_years)
- elsif remainder < 394200
+ elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
locale.t(:over_x_years, :count => distance_in_years)
else
locale.t(:almost_x_years, :count => distance_in_years + 1)
@@ -169,6 +173,9 @@ module ActionView
# "2 - February" instead of "February").
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
# Note: You can also use Rails' i18n functionality for this.
+ # * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer)
+ # 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
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
@@ -850,24 +857,36 @@ module ActionView
I18n.translate(key, :locale => @options[:locale])
end
- # Lookup month name for number.
- # month_name(1) => "January"
+ # Looks up month names by number (1-based):
+ #
+ # month_name(1) # => "January"
+ #
+ # If the <tt>:use_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => 1
+ #
+ # If the <tt>:use_two_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => '01'
+ #
+ # If the <tt>:add_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => "1 - January"
#
- # If <tt>:use_month_numbers</tt> option is passed
- # month_name(1) => 1
+ # If the <tt>:month_format_string</tt> option is passed:
#
- # If <tt>:use_two_month_numbers</tt> option is passed
- # month_name(1) => '01'
+ # month_name(1) # => "January (01)"
#
- # If <tt>:add_month_numbers</tt> option is passed
- # month_name(1) => "1 - January"
+ # depending on the format string.
def month_name(number)
if @options[:use_month_numbers]
number
elsif @options[:use_two_digit_numbers]
- sprintf "%02d", number
+ '%02d' % number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
+ elsif format_string = @options[:month_format_string]
+ format_string % {number: number, name: month_names[number]}
else
month_names[number]
end
@@ -946,7 +965,7 @@ module ActionView
:name => input_name_from_type(type)
}.merge!(@html_options)
select_options[:disabled] = 'disabled' if @options[:disabled]
- select_options[:class] = type if @options[:with_css_classes]
+ select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 5235962f9f..22bfd87d85 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -457,7 +457,7 @@ module ActionView
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
- # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
# its method signature is slightly different. Like +form_for+, it yields
# a FormBuilder object associated with a particular model object to a block,
# and within the block allows methods to be called on the builder to
@@ -746,6 +746,7 @@ module ActionView
# label(:post, :terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
@@ -1268,7 +1269,7 @@ module ActionView
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
- # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
# its method signature is slightly different. Like +form_for+, it yields
# a FormBuilder object associated with a particular model object to a block,
# and within the block allows methods to be called on the builder to
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index f625a9ff49..48f42947db 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -360,8 +360,8 @@ module ActionView
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- html_attributes[:selected] = option_value_selected?(value, selected)
- html_attributes[:disabled] = disabled && option_value_selected?(value, disabled)
+ html_attributes[:selected] ||= option_value_selected?(value, selected)
+ html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
html_attributes[:value] = value
content_tag_string(:option, text, html_attributes)
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 80f066b3be..1f931a300d 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -83,13 +83,17 @@ module ActionView
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:include_blank</tt> - If set to true, an empty option will be created.
- # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
+ # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
+ # * <tt>:selected</tt> - Provide a default selected value. The value provided should be the exact type the options are provided.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"]
+ # # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
+ #
# select_tag "people", "<option>David</option>".html_safe
# # => <select id="people" name="people"><option>David</option></select>
#
@@ -465,17 +469,23 @@ module ActionView
# # <strong>Ask me!</strong>
# # </button>
#
- # button_tag "Checkout", data: { :disable_with => "Please wait..." }
+ # button_tag "Checkout", data: { disable_with: "Please wait..." }
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
#
def button_tag(content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options = options.stringify_keys
+ if content_or_options.is_a? Hash
+ options = content_or_options
+ else
+ options ||= {}
+ end
- options.reverse_merge! 'name' => 'button', 'type' => 'submit'
+ options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys)
- content_tag :button, content_or_options || 'Button', options, &block
+ if block_given?
+ content_tag :button, options, &block
+ else
+ content_tag :button, content_or_options || 'Button', options
+ end
end
# Displays an image which when clicked will submit the form.
@@ -544,6 +554,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # color_field_tag 'name'
+ # # => <input id="name" name="name" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726'
+ # # => <input id="color" name="color" type="color" value="#DEF726" />
+ #
+ # color_field_tag 'color', nil, class: 'special_input'
+ # # => <input class="special_input" id="color" name="color" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
def color_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
end
@@ -552,6 +575,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # search_field_tag 'name'
+ # # => <input id="name" name="name" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here'
+ # # => <input id="search" name="search" type="search" value="Enter your search query here" />
+ #
+ # search_field_tag 'search', nil, class: 'special_input'
+ # # => <input class="special_input" id="search" name="search" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
def search_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
end
@@ -560,6 +596,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # telephone_field_tag 'name'
+ # # => <input id="name" name="name" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789'
+ # # => <input id="tel" name="tel" type="tel" value="0123456789" />
+ #
+ # telephone_field_tag 'tel', nil, class: 'special_input'
+ # # => <input class="special_input" id="tel" name="tel" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
def telephone_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
end
@@ -569,6 +618,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # date_field_tag 'name'
+ # # => <input id="name" name="name" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014'
+ # # => <input id="date" name="date" type="date" value="01/01/2014" />
+ #
+ # date_field_tag 'date', nil, class: 'special_input'
+ # # => <input class="special_input" id="date" name="date" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
def date_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
end
@@ -632,6 +694,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # url_field_tag 'name'
+ # # => <input id="name" name="name" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org'
+ # # => <input id="url" name="url" type="url" value="http://rubyonrails.org" />
+ #
+ # url_field_tag 'url', nil, class: 'special_input'
+ # # => <input class="special_input" id="url" name="url" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
def url_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
end
@@ -640,6 +715,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # email_field_tag 'name'
+ # # => <input id="name" name="name" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com'
+ # # => <input id="email" name="email" type="email" value="email@example.com" />
+ #
+ # email_field_tag 'email', nil, class: 'special_input'
+ # # => <input class="special_input" id="email" name="email" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
def email_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
end
@@ -651,12 +739,40 @@ module ActionView
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
# <tt>:max</tt> values.
+ # * <tt>:within</tt> - Same as <tt>:in</tt>.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
#
# ==== Examples
+ # number_field_tag 'quantity'
+ # # => <input id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', '1'
+ # # => <input id="quantity" name="quantity" type="number" value="1" />
+ #
+ # number_field_tag 'quantity', nil, class: 'special_input'
+ # # => <input class="special_input" id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1
+ # # => <input id="quantity" name="quantity" min="1" type="number" />
+ #
+ # number_field_tag 'quantity', nil, max: 9
+ # # => <input id="quantity" name="quantity" max="9" type="number" />
+ #
# number_field_tag 'quantity', nil, in: 1...10
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, within: 1...10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
+ # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" />
+ #
+ # number_field_tag 'quantity', '1', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
@@ -720,9 +836,11 @@ module ActionView
method_tag(method) + token_tag(authenticity_token)
end
- enforce_utf8 = html_options.delete("enforce_utf8") { true }
- tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag
- content_tag(:div, tags, :style => 'display:none')
+ if html_options.delete("enforce_utf8") { true }
+ utf8_enforcer_tag + method_tag
+ else
+ method_tag
+ end
end
def form_tag_html(html_options)
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index ad825cd1f1..7157a95146 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -384,20 +384,29 @@ module ActionView
def delegate_number_helper_method(method, number, options)
return unless number
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
+ options = escape_unsafe_options(options.symbolize_keys)
wrap_with_output_safety_handling(number, options.delete(:raise)) {
ActiveSupport::NumberHelper.public_send(method, number, options)
}
end
- def escape_unsafe_delimiters_and_separators(options)
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
- options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ def escape_unsafe_options(options)
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
options
end
+ def escape_units(units)
+ Hash[units.map do |k, v|
+ [k, ERB::Util.html_escape(v)]
+ end]
+ end
+
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
valid_float = valid_float?(number)
raise InvalidNumberError, number if raise_on_invalid && !valid_float
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 458086de96..ebfc35a4c7 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -12,6 +12,14 @@ module ActionView
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
+ # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
+ # type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
+ # performs html escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
+ # * <tt>:body</tt> - Renders the text passed in, and inherits the content
+ # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 3528381781..a9f3b0ffbc 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -42,7 +42,8 @@ module ActionView
# For example, a key +user_id+ would render as <tt>data-user-id</tt> and
# thus accessed as <tt>dataset.userId</tt>.
#
- # Values are encoded to JSON, with the exception of strings and symbols.
+ # Values are encoded to JSON, with the exception of strings, symbols and
+ # BigDecimals.
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
# from 1.4.3.
#
@@ -56,6 +57,9 @@ module ActionView
# tag("input", type: 'text', disabled: true)
# # => <input type="text" disabled="disabled" />
#
+ # tag("input", type: 'text', class: ["strong", "highlight"])
+ # # => <input class="strong highlight" type="text" />
+ #
# tag("img", src: "open & shut.png")
# # => <img src="open &amp; shut.png" />
#
@@ -75,7 +79,7 @@ module ActionView
# Set escape to false to disable attribute value escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -84,6 +88,8 @@ module ActionView
# # => <p>Hello world!</p>
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
# # => <div class="strong"><p>Hello world!</p></div>
+ # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
+ # # => <div class="strong highlight">Hello world!</div>
# content_tag("select", options, multiple: true)
# # => <select multiple="multiple">...options...</select>
#
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 9b77ebeb1b..6242a2a085 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -27,10 +27,11 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
- hidden_name = @html_options[:name] || "#{tag_name}[]"
- hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil)
-
- rendered_collection + hidden
+ if @options.fetch(:include_hidden, true)
+ rendered_collection + hidden_field
+ else
+ rendered_collection
+ end
end
private
@@ -38,6 +39,18 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
+
+ def hidden_field
+ hidden_name = @html_options[:name]
+
+ hidden_name ||= if @options.has_key?(:index)
+ "#{tag_name_with_index(@options[:index])}[]"
+ else
+ "#{tag_name}[]"
+ end
+
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 991f32cea2..8050638363 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -44,7 +44,7 @@ module ActionView
def default_html_options_for_collection(item, value) #:nodoc:
html_options = @html_options.dup
- [:checked, :selected, :disabled].each do |option|
+ [:checked, :selected, :disabled, :readonly].each do |option|
current_value = @options[option]
next if current_value.nil?
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 35d3ba8434..6335e3dd4d 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -36,7 +36,7 @@ module ActionView
content = @template_object.capture(&block)
else
content = if @content.blank?
- @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
if object.respond_to?(:to_model)
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 3ae1df04fe..0bc40874d9 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -38,10 +38,10 @@ module ActionView
# If the user has specified rescue_format then pass it all through, otherwise use
# raise and do the work ourselves
- if options.key?(:raise) || options.key?(:rescue_format)
- raise_error = options[:raise] || options[:rescue_format]
- else
- raise_error = false
+ options[:raise] ||= ActionView::Base.raise_on_missing_translations
+
+ raise_error = options[:raise] || options.key?(:rescue_format)
+ unless raise_error
options[:raise] = true
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 56dd7a4390..894616a449 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -82,7 +82,7 @@ module ActionView
# to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
# * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -232,6 +232,11 @@ module ActionView
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
+ # <%= button_to "New", new_articles_path %>
+ # # => "<form method="post" action="/articles/new" class="button_to">
+ # # <div><input value="New" type="submit" /></div>
+ # # </form>"
+ #
# <%= button_to [:make_happy, @user] do %>
# Make happy <strong><%= @user.name %></strong>
# <% end %>
@@ -318,7 +323,7 @@ module ActionView
inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
end
end
- content_tag('form', content_tag('div', inner_tags), form_options)
+ content_tag('form', inner_tags, form_options)
end
# Creates a link tag of the given +name+ using a URL created by the set of
@@ -384,15 +389,7 @@ module ActionView
# # If not...
# # => <a href="/accounts/signup">Reply</a>
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
- if condition
- if block_given?
- block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
- else
- ERB::Util.html_escape(name)
- end
- else
- link_to(name, options, html_options)
- end
+ link_to_if !condition, name, options, html_options, &block
end
# Creates a link tag of the given +name+ using a URL created by the set of
@@ -416,7 +413,15 @@ module ActionView
# # If they are logged in...
# # => <a href="/accounts/show/3">my_username</a>
def link_to_if(condition, name, options = {}, html_options = {}, &block)
- link_to_unless !condition, name, options, html_options, &block
+ if condition
+ link_to(name, options, html_options)
+ else
+ if block_given?
+ block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
+ else
+ ERB::Util.html_escape(name)
+ end
+ end
end
# Creates a mailto link tag to the specified +email_address+, which is
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index ffa67649da..9ee05bd816 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -420,7 +420,7 @@ module ActionView
end
def _include_layout?(options)
- (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
+ (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
end
end
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index e07d9b6314..855fed0190 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -1,6 +1,7 @@
require 'thread_safe'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/module/attribute_accessors'
+require 'action_view/template/resolver'
module ActionView
# = Action View Lookup Context
@@ -158,7 +159,14 @@ module ActionView
def detail_args_for(options)
return @details, details_key if options.empty? # most common path.
user_details = @details.merge(options)
- [user_details, DetailsKey.get(user_details)]
+
+ if @cache
+ details_key = DetailsKey.get(user_details)
+ else
+ details_key = nil
+ end
+
+ [user_details, details_key]
end
# Support legacy foo.erb names even though we now ignore .erb
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 668831dff3..be17097428 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -21,8 +21,14 @@ module ActionView
def determine_template(options) #:nodoc:
keys = options.fetch(:locals, {}).keys
- if options.key?(:text)
+ if options.key?(:body)
+ Template::Text.new(options[:body])
+ elsif options.key?(:text)
Template::Text.new(options[:text], formats.first)
+ elsif options.key?(:plain)
+ Template::Text.new(options[:plain])
+ elsif options.key?(:html)
+ Template::HTML.new(options[:html], formats.first)
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
@@ -35,7 +41,7 @@ module ActionView
find_template(options[:template], options[:prefixes], false, keys, @details)
end
else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 99b95fdfb7..017302d40f 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -94,13 +94,13 @@ module ActionView
variant = options[:variant]
lookup_context.rendered_format = nil if options[:formats]
- lookup_context.variants = [variant] if variant
+ lookup_context.variants = variant if variant
view_renderer.render(view_context, options)
end
# Assign the rendered format to lookup context.
- def _process_format(format) #:nodoc:
+ def _process_format(format, options = {}) #:nodoc:
super
lookup_context.formats = [format.to_sym]
lookup_context.rendered_format = lookup_context.formats.first
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 33be06cbf7..b9e4b590e7 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -77,10 +77,10 @@ module ActionView
case options
when String
options
- when nil, Hash
- options ||= {}
- options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
- super
+ when nil
+ super({:only_path => true})
+ when Hash
+ super({ :only_path => options[:host].nil? }.merge!(options.symbolize_keys))
when :back
_back_url
when Array
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 9b0619f1aa..9d39d02a37 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -90,13 +90,14 @@ module ActionView
eager_autoload do
autoload :Error
autoload :Handlers
+ autoload :HTML
autoload :Text
autoload :Types
end
extend Template::Handlers
- attr_accessor :locals, :formats, :virtual_path
+ attr_accessor :locals, :formats, :variants, :virtual_path
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
@@ -122,6 +123,7 @@ module ActionView
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
@formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
+ @variants = [details[:variant]]
@compile_mutex = Mutex.new
end
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
new file mode 100644
index 0000000000..0321f819b5
--- /dev/null
+++ b/actionview/lib/action_view/template/html.rb
@@ -0,0 +1,34 @@
+module ActionView #:nodoc:
+ # = Action View HTML Template
+ class Template
+ class HTML #:nodoc:
+ attr_accessor :type
+
+ def initialize(string, type = nil)
+ @string = string.to_s
+ @type = Types[type] || type if type
+ @type ||= Types[:html]
+ end
+
+ def identifier
+ 'html template'
+ end
+
+ def inspect
+ 'html template'
+ end
+
+ def to_str
+ ERB::Util.h(@string)
+ end
+
+ def render(*args)
+ to_str
+ end
+
+ def formats
+ [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 3a3b74cdd5..05f0c301e7 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -154,7 +154,8 @@ module ActionView
cached = nil
templates.each do |t|
t.locals = locals
- t.formats = details[:formats] || [:html] if t.formats.empty?
+ t.formats = details[:formats] || [:html] if t.formats.empty?
+ t.variants = details[:variants] || [] if t.variants.empty?
t.virtual_path ||= (cached ||= build_path(*path_info))
end
end
@@ -189,13 +190,15 @@ module ActionView
}
template_paths.map { |template|
- handler, format = extract_handler_and_format(template, formats)
- contents = File.binread template
+ handler, format, variant = extract_handler_and_format_and_variant(template, formats)
+ contents = File.binread(template)
Template.new(contents, File.expand_path(template), handler,
:virtual_path => path.virtual,
:format => format,
- :updated_at => mtime(template))
+ :variant => variant,
+ :updated_at => mtime(template)
+ )
}
end
@@ -225,10 +228,10 @@ module ActionView
File.mtime(p)
end
- # Extract handler and formats from path. If a format cannot be a found neither
+ # Extract handler, formats and variant from path. If a format cannot be found neither
# from the path, or the handler, we should return the array of formats given
# to the resolver.
- def extract_handler_and_format(path, default_formats)
+ def extract_handler_and_format_and_variant(path, default_formats)
pieces = File.basename(path).split(".")
pieces.shift
@@ -240,10 +243,10 @@ module ActionView
end
handler = Template.handler_for_extension(extension)
- format = pieces.last && pieces.last.split(EXTENSIONS[:variants], 2).first # remove variant from format
+ format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
format &&= Template::Types[format]
- [handler, format]
+ [handler, format, variant]
end
end
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index 859c7bc3ce..04f5b8d17a 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -27,7 +27,7 @@ module ActionView #:nodoc:
end
def formats
- [@type.to_sym]
+ [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
end
end
end
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 3145446114..9e8e6f43d5 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -235,7 +235,8 @@ module ActionView
:@options,
:@test_passed,
:@view,
- :@view_context_class
+ :@view_context_class,
+ :@_subscribers
]
def _user_defined_ivars
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index af53ad3b25..dfb7d463b4 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -30,9 +30,13 @@ module ActionView #:nodoc:
@hash.each do |_path, array|
source, updated_at = array
next unless _path =~ query
- handler, format = extract_handler_and_format(_path, formats)
+ handler, format, variant = extract_handler_and_format_and_variant(_path, formats)
templates << Template.new(source, _path, handler,
- :virtual_path => path.virtual, :format => format, :updated_at => updated_at)
+ :virtual_path => path.virtual,
+ :format => format,
+ :variant => variant,
+ :updated_at => updated_at
+ )
end
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
@@ -41,8 +45,8 @@ module ActionView #:nodoc:
class NullResolver < PathResolver
def query(path, exts, formats)
- handler, format = extract_handler_and_format(path, formats)
- [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]
+ handler, format, variant = extract_handler_and_format_and_variant(path, formats)
+ [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)]
end
end
diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb
index edb6d8f116..f55d3fdaef 100644
--- a/actionview/lib/action_view/version.rb
+++ b/actionview/lib/action_view/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActionView
- # Returns the version of the currently loaded ActionView as a Gem::Version
+ # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActionView.version.segments
- STRING = ActionView.version.to_s
+ gem_version
end
end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 0a9628da8d..0a62f49f35 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -59,12 +59,13 @@ class FormHelperActiveRecordTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
- txt << %{</div>}
+
+ txt
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
@@ -88,4 +89,4 @@ class FormHelperActiveRecordTest < ActionView::TestCase
form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
end
-end \ No newline at end of file
+end
diff --git a/actionview/test/fixtures/customers/_customer.xml.erb b/actionview/test/fixtures/customers/_customer.xml.erb
new file mode 100644
index 0000000000..d3f1e0768f
--- /dev/null
+++ b/actionview/test/fixtures/customers/_customer.xml.erb
@@ -0,0 +1 @@
+<greeting><%= greeting %></greeting><name><%= customer.name %></name> \ No newline at end of file
diff --git a/actionview/test/fixtures/digestor/messages/new.html+iphone.erb b/actionview/test/fixtures/digestor/messages/new.html+iphone.erb
new file mode 100644
index 0000000000..791e1d36b4
--- /dev/null
+++ b/actionview/test/fixtures/digestor/messages/new.html+iphone.erb
@@ -0,0 +1,15 @@
+<%# Template Dependency: messages/message %>
+
+<%= render "header" %>
+<%= render "comments/comments" %>
+
+<%= render "messages/actions/move" %>
+
+<%= render @message.history.events %>
+
+<%# render "something_missing" %>
+<%# render "something_missing_1" %>
+
+<%
+ # Template Dependency: messages/form
+%> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/hello_world.html+phone.erb b/actionview/test/fixtures/test/hello_world.html+phone.erb
new file mode 100644
index 0000000000..b4f236f878
--- /dev/null
+++ b/actionview/test/fixtures/test/hello_world.html+phone.erb
@@ -0,0 +1 @@
+Hello phone! \ No newline at end of file
diff --git a/actionview/test/fixtures/test/hello_world.text+phone.erb b/actionview/test/fixtures/test/hello_world.text+phone.erb
new file mode 100644
index 0000000000..611e2ee442
--- /dev/null
+++ b/actionview/test/fixtures/test/hello_world.text+phone.erb
@@ -0,0 +1 @@
+Hello texty phone! \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 3ca71d3376..651978ed80 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -195,9 +195,9 @@ class AssetTagHelperTest < ActionView::TestCase
}
FaviconLinkToTag = {
- %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />),
+ %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/x-icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />),
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />)
}
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 5f09aef249..b86ae910c4 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -326,6 +326,16 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_month(8, :add_month_numbers => true)
end
+ def test_select_month_with_format_string
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n)
+ expected << "</select>\n"
+
+ format_string = '%{name} (%<number>02d)'
+ assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :month_format_string => format_string)
+ assert_dom_equal expected, select_month(8, :month_format_string => format_string)
+ end
+
def test_select_month_with_numbers_and_names_with_abbv
expected = %(<select id="date_month" name="date[month]">\n)
expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
@@ -1030,6 +1040,22 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true})
end
+ def test_select_date_with_css_classes_option_and_html_class_option
+ expected = %(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]" class="datetime optional month">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]" class="datetime optional day">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</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" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">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)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}, { class: 'datetime optional' })
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index 7a9b4b26ac..df3a0602d1 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'abstract_unit'
require 'action_view/dependency_tracker'
@@ -52,23 +54,127 @@ class ERBTrackerTest < Minitest::Test
def test_dependency_of_erb_template_with_number_in_filename
template = FakeTemplate.new("<%# render 'messages/message123' %>", :erb)
- tracker = make_tracker('messages/_message123', template)
+ tracker = make_tracker("messages/_message123", template)
assert_equal ["messages/message123"], tracker.dependencies
end
def test_finds_dependency_in_correct_directory
template = FakeTemplate.new("<%# render(message.topic) %>", :erb)
- tracker = make_tracker('messages/_message', template)
+ tracker = make_tracker("messages/_message", template)
assert_equal ["topics/topic"], tracker.dependencies
end
def test_finds_dependency_in_correct_directory_with_underscore
template = FakeTemplate.new("<%# render(message_type.messages) %>", :erb)
- tracker = make_tracker('message_types/_message_type', template)
+ tracker = make_tracker("message_types/_message_type", template)
assert_equal ["messages/message"], tracker.dependencies
end
-end
+ def test_dependency_of_erb_template_with_no_spaces_after_render
+ template = FakeTemplate.new("<%# render'messages/message' %>", :erb)
+ tracker = make_tracker("messages/_message", template)
+
+ assert_equal ["messages/message"], tracker.dependencies
+ end
+
+ def test_finds_no_dependency_when_render_begins_the_name_of_an_identifier
+ template = FakeTemplate.new("<%# rendering 'it useless' %>", :erb)
+ tracker = make_tracker("resources/_resource", template)
+
+ assert_equal [], tracker.dependencies
+ end
+
+ def test_finds_no_dependency_when_render_ends_the_name_of_another_method
+ template = FakeTemplate.new("<%# surrender 'to reason' %>", :erb)
+ tracker = make_tracker("resources/_resource", template)
+
+ assert_equal [], tracker.dependencies
+ end
+
+ def test_finds_dependency_on_multiline_render_calls
+ template = FakeTemplate.new("<%#
+ render :object => @all_posts,
+ :partial => 'posts' %>", :erb)
+
+ tracker = make_tracker("some/_little_posts", template)
+
+ assert_equal ["some/posts"], tracker.dependencies
+ end
+
+ def test_finds_multiple_unrelated_odd_dependencies
+ template = FakeTemplate.new("
+ <%# render('shared/header', title: 'Title') %>
+ <h2>Section title</h2>
+ <%# render@section %>
+ ", :erb)
+
+ tracker = make_tracker("multiple/_dependencies", template)
+
+ assert_equal ["shared/header", "sections/section"], tracker.dependencies
+ end
+
+ def test_finds_dependencies_for_all_kinds_of_identifiers
+ template = FakeTemplate.new("
+ <%# render $globals %>
+ <%# render @instance_variables %>
+ <%# render @@class_variables %>
+ ", :erb)
+
+ tracker = make_tracker("identifiers/_all", template)
+
+ assert_equal [
+ "globals/global",
+ "instance_variables/instance_variable",
+ "class_variables/class_variable"
+ ], tracker.dependencies
+ end
+
+ def test_finds_dependencies_on_method_chains
+ template = FakeTemplate.new("<%# render @parent.child.grandchildren %>", :erb)
+ tracker = make_tracker("method/_chains", template)
+
+ assert_equal ["grandchildren/grandchild"], tracker.dependencies
+ end
+
+ def test_finds_dependencies_with_special_characters
+ template = FakeTemplate.new("<%# render @pokémon, partial: 'ピカチュウ' %>", :erb)
+ tracker = make_tracker("special/_characters", template)
+
+ assert_equal ["special/ピカチュウ"], tracker.dependencies
+ end
+
+ def test_finds_dependencies_with_quotes_within
+ template = FakeTemplate.new(%{
+ <%# render "single/quote's" %>
+ <%# render 'double/quote"s' %>
+ }, :erb)
+
+ tracker = make_tracker("quotes/_single_and_double", template)
+
+ assert_equal ["single/quote's", 'double/quote"s'], tracker.dependencies
+ end
+
+ def test_finds_dependencies_with_extra_spaces
+ template = FakeTemplate.new(%{
+ <%= render "header" %>
+ <%= render partial: "form" %>
+ <%= render @message %>
+ <%= render ( @message.events ) %>
+ <%= render :collection => @message.comments,
+ :partial => "comments/comment" %>
+ }, :erb)
+
+ tracker = make_tracker("spaces/_extra", template)
+
+ assert_equal [
+ "spaces/header",
+ "spaces/form",
+ "messages/message",
+ "events/event",
+ "comments/comment"
+ ], tracker.dependencies
+ end
+end
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index 779a7fb53c..47e1f6a6e5 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -15,18 +15,30 @@ end
class FixtureFinder
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
- attr_reader :details
+ attr_reader :details
+ attr_accessor :formats
+ attr_accessor :variants
def initialize
- @details = {}
+ @details = {}
+ @formats = []
+ @variants = []
end
def details_key
details.hash
end
- def find(logical_name, keys, partial, options)
- FixtureTemplate.new("digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb")
+ def find(name, prefixes = [], partial = false, keys = [], options = {})
+ partial_name = partial ? name.gsub(%r|/([^/]+)$|, '/_\1') : name
+ format = @formats.first.to_s
+ format += "+#{@variants.first}" if @variants.any?
+
+ FixtureTemplate.new("digestor/#{partial_name}.#{format}.erb")
+ end
+
+ def disable_cache(&block)
+ yield
end
end
@@ -88,13 +100,13 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_logging_of_missing_template
- assert_logged "Couldn't find template for digesting: messages/something_missing.html" do
+ assert_logged "Couldn't find template for digesting: messages/something_missing" do
digest("messages/show")
end
end
def test_logging_of_missing_template_ending_with_number
- assert_logged "Couldn't find template for digesting: messages/something_missing_1.html" do
+ assert_logged "Couldn't find template for digesting: messages/something_missing_1" do
digest("messages/show")
end
end
@@ -194,6 +206,13 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_variants
+ assert_digest_difference("messages/new", false, variants: [:iphone]) do
+ change_template("messages/new", :iphone)
+ change_template("messages/_header", :iphone)
+ end
+ end
+
def test_dependencies_via_options_results_in_different_digest
digest_plain = digest("comments/_comment")
digest_fridge = digest("comments/_comment", dependencies: ["fridge"])
@@ -242,6 +261,7 @@ class TemplateDigestorTest < ActionView::TestCase
ActionView::Resolver.caching = resolver_before
end
+
private
def assert_logged(message)
old_logger = ActionView::Base.logger
@@ -258,26 +278,33 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
- def assert_digest_difference(template_name, persistent = false)
- previous_digest = digest(template_name)
+ def assert_digest_difference(template_name, persistent = false, options = {})
+ previous_digest = digest(template_name, options)
ActionView::Digestor.cache.clear unless persistent
yield
- assert previous_digest != digest(template_name), "digest didn't change"
+ assert previous_digest != digest(template_name, options), "digest didn't change"
ActionView::Digestor.cache.clear
end
- def digest(template_name, options={})
- ActionView::Digestor.digest(template_name, :html, finder, options)
+ def digest(template_name, options = {})
+ options = options.dup
+
+ finder.formats = [:html]
+ finder.variants = options.delete(:variants) || []
+
+ ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options))
end
def finder
@finder ||= FixtureFinder.new
end
- def change_template(template_name)
- File.open("digestor/#{template_name}.html.erb", "w") do |f|
+ def change_template(template_name, variant = nil)
+ variant = "+#{variant}" if variant.present?
+
+ File.open("digestor/#{template_name}.html#{variant}.erb", "w") do |f|
f.write "\nTHIS WAS CHANGED!"
end
end
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index 18632465db..5e991d87ad 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -68,6 +68,23 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=radio][value=false][disabled=disabled]'
end
+ test 'collection radio accepts multiple readonly items' do
+ collection = [[1, true], [0, false], [2, 'other']]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => [true, false]
+
+ assert_select 'input[type=radio][value=true][readonly=readonly]'
+ assert_select 'input[type=radio][value=false][readonly=readonly]'
+ assert_no_select 'input[type=radio][value=other][readonly=readonly]'
+ end
+
+ test 'collection radio accepts single readonly item' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => true
+
+ assert_select 'input[type=radio][value=true][readonly=readonly]'
+ assert_no_select 'input[type=radio][value=false][readonly=readonly]'
+ end
+
test 'collection radio accepts html options as input' do
collection = [[1, true], [0, false]]
with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio'
@@ -204,6 +221,20 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1
end
+ test 'collection check boxes generates a hidden field with index if it was provided' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name, { index: 322 }
+
+ assert_select "input[type=hidden][name='user[322][category_ids][]'][value=]", count: 1
+ end
+
+ test 'collection check boxes does not generate a hidden field if include_hidden option is false' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false
+
+ assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 0
+ end
+
test 'collection check boxes accepts a collection and generate a series of checkboxes with labels for label method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name
@@ -318,6 +349,33 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
end
+ test 'collection check boxes accepts multiple readonly items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
+ test 'collection check boxes accepts single readonly item' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => 1
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
+ test 'collection check boxes accepts a proc to readonly items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => proc { |i| i.first == 1 }
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
test 'collection check boxes accepts html options' do
collection = [[1, 'Category 1'], [2, 'Category 2']]
with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check'
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index fe82349265..0ad0ae6b4b 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -143,75 +143,68 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_strings
- old_locale, I18n.locale = I18n.locale, :label
- assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body"))
- ensure
- I18n.locale = old_locale
+ with_locale :label do
+ assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body"))
+ end
end
def test_label_with_human_attribute_name
- old_locale, I18n.locale = I18n.locale, :label
- assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost))
- ensure
- I18n.locale = old_locale
+ with_locale :label do
+ assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost))
+ end
end
def test_label_with_locales_symbols
- old_locale, I18n.locale = I18n.locale, :label
- assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
- ensure
- I18n.locale = old_locale
+ with_locale :label do
+ assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
+ end
end
def test_label_with_locales_and_options
- old_locale, I18n.locale = I18n.locale, :label
- assert_dom_equal(
- '<label for="post_body" class="post_body">Write entire text here</label>',
- label(:post, :body, class: "post_body")
- )
- ensure
- I18n.locale = old_locale
+ with_locale :label do
+ assert_dom_equal(
+ '<label for="post_body" class="post_body">Write entire text here</label>',
+ label(:post, :body, class: "post_body")
+ )
+ end
end
def test_label_with_locales_and_value
- old_locale, I18n.locale = I18n.locale, :label
- assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red"))
- ensure
- I18n.locale = old_locale
+ with_locale :label do
+ assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red"))
+ end
end
def test_label_with_locales_and_nested_attributes
- old_locale, I18n.locale = I18n.locale, :label
- form_for(@post, html: { id: 'create-post' }) do |f|
- f.fields_for(:comments) do |cf|
- concat cf.label(:body)
+ with_locale :label do
+ form_for(@post, html: { id: 'create-post' }) do |f|
+ f.fields_for(:comments) do |cf|
+ concat cf.label(:body)
+ end
end
- end
- expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
- '<label for="post_comments_attributes_0_body">Write body here</label>'
- end
+ expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
+ '<label for="post_comments_attributes_0_body">Write body here</label>'
+ end
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
+ assert_dom_equal expected, output_buffer
+ end
end
def test_label_with_locales_fallback_and_nested_attributes
- old_locale, I18n.locale = I18n.locale, :label
- form_for(@post, html: { id: 'create-post' }) do |f|
- f.fields_for(:tags) do |cf|
- concat cf.label(:value)
+ with_locale :label do
+ form_for(@post, html: { id: 'create-post' }) do |f|
+ f.fields_for(:tags) do |cf|
+ concat cf.label(:value)
+ end
end
- end
- expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
- '<label for="post_tags_attributes_0_value">Tag</label>'
- end
+ expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
+ '<label for="post_tags_attributes_0_value">Tag</label>'
+ end
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
+ assert_dom_equal expected, output_buffer
+ end
end
def test_label_with_for_attribute_as_symbol
@@ -272,6 +265,13 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_label_with_block_and_html
+ assert_dom_equal(
+ '<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>',
+ label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe }
+ )
+ end
+
def test_label_with_block_and_options
assert_dom_equal(
'<label for="my_for">The title, please:</label>',
@@ -1427,7 +1427,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts", "new_post", "new_post") do
"<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
"<label for='post_1_tag_ids_1'>Tag 1</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -1811,69 +1811,61 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_as_new_record_and_locale_strings
- old_locale, I18n.locale = I18n.locale, :submit
+ with_locale :submit do
+ @post.persisted = false
+ @post.stubs(:to_key).returns(nil)
+ form_for(@post) do |f|
+ concat f.submit
+ end
- @post.persisted = false
- @post.stubs(:to_key).returns(nil)
- form_for(@post) do |f|
- concat f.submit
- end
+ expected = whole_form('/posts', 'new_post', 'new_post') do
+ "<input name='commit' type='submit' value='Create Post' />"
+ end
- expected = whole_form('/posts', 'new_post', 'new_post') do
- "<input name='commit' type='submit' value='Create Post' />"
+ assert_dom_equal expected, output_buffer
end
-
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
end
def test_submit_with_object_as_existing_record_and_locale_strings
- old_locale, I18n.locale = I18n.locale, :submit
+ with_locale :submit do
+ form_for(@post) do |f|
+ concat f.submit
+ end
- form_for(@post) do |f|
- concat f.submit
- end
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
+ "<input name='commit' type='submit' value='Confirm Post changes' />"
+ end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
- "<input name='commit' type='submit' value='Confirm Post changes' />"
+ assert_dom_equal expected, output_buffer
end
-
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
end
def test_submit_without_object_and_locale_strings
- old_locale, I18n.locale = I18n.locale, :submit
+ with_locale :submit do
+ form_for(:post) do |f|
+ concat f.submit class: "extra"
+ end
- form_for(:post) do |f|
- concat f.submit class: "extra"
- end
+ expected = whole_form do
+ "<input name='commit' class='extra' type='submit' value='Save changes' />"
+ end
- expected = whole_form do
- "<input name='commit' class='extra' type='submit' value='Save changes' />"
+ assert_dom_equal expected, output_buffer
end
-
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
end
def test_submit_with_object_and_nested_lookup
- old_locale, I18n.locale = I18n.locale, :submit
+ with_locale :submit do
+ form_for(@post, as: :another_post) do |f|
+ concat f.submit
+ end
- form_for(@post, as: :another_post) do |f|
- concat f.submit
- end
+ expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
+ "<input name='commit' type='submit' value='Update your Post' />"
+ end
- expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
- "<input name='commit' type='submit' value='Update your Post' />"
+ assert_dom_equal expected, output_buffer
end
-
- assert_dom_equal expected, output_buffer
- ensure
- I18n.locale = old_locale
end
def test_nested_fields_for
@@ -2405,6 +2397,18 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_label_translation_with_more_than_10_records
+ @post.comments = Array.new(11) { |id| Comment.new(id + 1) }
+
+ I18n.expects(:t).with('post.comments.body', default: [:"comment.body", ''], scope: "helpers.label").times(11).returns "Write body here"
+
+ form_for(@post) do |f|
+ f.fields_for(:comments) do |cf|
+ concat cf.label(:body)
+ end
+ end
+ end
+
def test_nested_fields_for_with_existing_records_on_a_supplied_nested_attributes_collection_different_from_record_one
comments = Array.new(2) { |id| Comment.new(id + 1) }
@post.comments = []
@@ -3023,12 +3027,13 @@ class FormHelperTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
- txt << %{</div>}
+
+ txt
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
@@ -3052,4 +3057,11 @@ class FormHelperTest < ActionView::TestCase
def protect_against_forgery?
false
end
+
+ def with_locale(testing_locale = :label)
+ old_locale, I18n.locale = I18n.locale, testing_locale
+ yield
+ ensure
+ I18n.locale = old_locale
+ end
end
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 50e9d132a7..fbafb7aa08 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -119,6 +119,26 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_array_options_for_select_with_custom_defined_selected
+ assert_dom_equal(
+ "<option selected=\"selected\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>",
+ options_for_select([
+ ['Richard Bandler', 1, { type: 'Coach', selected: 'selected' }],
+ ['Richard Bandler', 1, { type: 'Coachee' }]
+ ])
+ )
+ end
+
+ def test_array_options_for_select_with_custom_defined_disabled
+ assert_dom_equal(
+ "<option disabled=\"disabled\" type=\"Coach\" value=\"1\">Richard Bandler</option>\n<option type=\"Coachee\" value=\"1\">Richard Bandler</option>",
+ options_for_select([
+ ['Richard Bandler', 1, { type: 'Coach', disabled: 'disabled' }],
+ ['Richard Bandler', 1, { type: 'Coachee' }]
+ ])
+ )
+ end
+
def test_array_options_for_select_with_selection
assert_dom_equal(
"<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
@@ -813,7 +833,7 @@ class FormOptionsHelperTest < ActionView::TestCase
select("post", "category", %w( one two ), :selected => 'two', :prompt => true)
)
end
-
+
def test_select_with_disabled_array
@post = Post.new
@post.category = "<mus>"
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 0d5831dc6f..18c739674a 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -14,12 +14,15 @@ class FormTagHelperTest < ActionView::TestCase
method = options[:method]
enforce_utf8 = options.fetch(:enforce_utf8, true)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />} if enforce_utf8
- if method && !%w(get post).include?(method.to_s)
- txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ ''.tap do |txt|
+ if enforce_utf8
+ txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ end
+
+ if method && !%w(get post).include?(method.to_s)
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
end
- txt << %{</div>}
end
def form_text(action = "http://www.example.com", options = {})
@@ -476,6 +479,11 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal('<button name="temptation" type="button"><strong>Do not press me</strong></button>', output)
end
+ def test_button_tag_defaults_with_block_and_options
+ output = button_tag(:name => 'temptation', :value => 'within') { content_tag(:strong, 'Do not press me') }
+ assert_dom_equal('<button name="temptation" value="within" type="submit" ><strong>Do not press me</strong></button>', output)
+ end
+
def test_button_tag_with_confirmation
assert_dom_equal(
%(<button name="button" type="submit" data-confirm="Are you sure?">Save</button>),
diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb
new file mode 100644
index 0000000000..549c12c88c
--- /dev/null
+++ b/actionview/test/template/html_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class HTMLTest < ActiveSupport::TestCase
+ test 'formats returns symbol for recognized MIME type' do
+ assert_equal [:html], ActionView::Template::HTML.new('', :html).formats
+ end
+
+ test 'formats returns string for recognized MIME type when MIME does not have symbol' do
+ foo = Mime::Type.lookup("foo")
+ assert_nil foo.to_sym
+ assert_equal ['foo'], ActionView::Template::HTML.new('', foo).formats
+ end
+
+ test 'formats returns string for unknown MIME type' do
+ assert_equal ['foo'], ActionView::Template::HTML.new('', 'foo').formats
+ end
+end
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index ce9485e146..4f7823045e 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -93,6 +93,20 @@ class LookupContextTest < ActiveSupport::TestCase
assert_equal "Hey verden", template.source
end
+ test "find templates with given variants" do
+ @lookup_context.formats = [:html]
+ @lookup_context.variants = [:phone]
+
+ template = @lookup_context.find("hello_world", %w(test))
+ assert_equal "Hello phone!", template.source
+
+ @lookup_context.variants = [:phone]
+ @lookup_context.formats = [:text]
+
+ template = @lookup_context.find("hello_world", %w(test))
+ assert_equal "Hello texty phone!", template.source
+ end
+
test "found templates respects given formats if one cannot be found from template or handler" do
ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil)
@lookup_context.formats = [:text]
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index be336ea3fb..adb888319d 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -8,6 +8,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "555-1234", number_to_phone(5551234)
assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123)
assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "")
+ assert_equal "+&lt;script&gt;&lt;/script&gt;8005551212", number_to_phone(8005551212, country_code: "<script></script>", delimiter: "")
+ assert_equal "8005551212 x &lt;script&gt;&lt;/script&gt;", number_to_phone(8005551212, extension: "<script></script>", delimiter: "")
end
def test_number_to_currency
@@ -16,14 +18,23 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0)
assert_equal "1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", unit: raw("K&#269;"), format: "%n %u", negative_format: "%n - %u")
assert_equal "&amp;pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "&pound;")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("1234567890.50", format: "<b>%n</b> %u")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u")
+ assert_equal "&lt;b&gt;1,234,567,890.50&lt;/b&gt; $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u")
end
def test_number_to_percentage
assert_equal nil, number_to_percentage(nil)
assert_equal "100.000%", number_to_percentage(100)
+ assert_equal "100.000 %", number_to_percentage(100, format: '%n %')
+ assert_equal "&lt;b&gt;100.000&lt;/b&gt; %", number_to_percentage(100, format: '<b>%n</b> %')
+ assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw('<b>%n</b> %'))
assert_equal "100%", number_to_percentage(100, precision: 0)
assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true)
assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",")
+ assert_equal "98a%", number_to_percentage("98a")
+ assert_equal "NaN%", number_to_percentage(Float::NAN)
+ assert_equal "Inf%", number_to_percentage(Float::INFINITY)
end
def test_number_with_delimiter
@@ -52,6 +63,31 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false)
end
+ def test_number_to_human_escape_units
+ volume = { unit: "<b>ml</b>", thousand: "<b>lt</b>", million: "<b>m3</b>", trillion: "<b>km3</b>", quadrillion: "<b>Pl</b>" }
+ assert_equal '123 &lt;b&gt;lt&lt;/b&gt;', number_to_human(123456, :units => volume)
+ assert_equal '12 &lt;b&gt;ml&lt;/b&gt;', number_to_human(12, :units => volume)
+ assert_equal '1.23 &lt;b&gt;m3&lt;/b&gt;', number_to_human(1234567, :units => volume)
+ assert_equal '1.23 &lt;b&gt;km3&lt;/b&gt;', number_to_human(1_234_567_000_000, :units => volume)
+ assert_equal '1.23 &lt;b&gt;Pl&lt;/b&gt;', number_to_human(1_234_567_000_000_000, :units => volume)
+
+ #Including fractionals
+ distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>",
+ ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>",
+ micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"}
+ assert_equal '1.23 &lt;b&gt;mm&lt;/b&gt;', number_to_human(0.00123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;cm&lt;/b&gt;', number_to_human(0.0123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;dm&lt;/b&gt;', number_to_human(0.123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;m&lt;/b&gt;', number_to_human(1.23, :units => distance)
+ assert_equal '1.23 &lt;b&gt;dam&lt;/b&gt;', number_to_human(12.3, :units => distance)
+ assert_equal '1.23 &lt;b&gt;hm&lt;/b&gt;', number_to_human(123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;km&lt;/b&gt;', number_to_human(1230, :units => distance)
+ assert_equal '1.23 &lt;b&gt;um&lt;/b&gt;', number_to_human(0.00000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;nm&lt;/b&gt;', number_to_human(0.00000000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;pm&lt;/b&gt;', number_to_human(0.00000000000123, :units => distance)
+ assert_equal '1.23 &lt;b&gt;fm&lt;/b&gt;', number_to_human(0.00000000000000123, :units => distance)
+ end
+
def test_number_helpers_escape_delimiter_and_separator
assert_equal "111&lt;script&gt;&lt;/script&gt;111&lt;script&gt;&lt;/script&gt;1111", number_to_phone(1111111111, delimiter: "<script></script>")
@@ -73,6 +109,12 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "100&lt;script&gt;&lt;/script&gt;000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>")
end
+ def test_number_to_human_with_custom_translation_scope
+ I18n.backend.store_translations 'ts',
+ :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human)
+ end
+
def test_number_helpers_outputs_are_html_safe
assert number_to_human(1).html_safe?
assert !number_to_human("<script></script>").html_safe?
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 055a273cc3..ca508abfb8 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -22,7 +22,7 @@ module RenderTestCases
def test_render_without_options
e = assert_raises(ArgumentError) { @view.render() }
- assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message
+ assert_match(/You invoked render but did not give any of (.+) option./, e.message)
end
def test_render_file
@@ -304,6 +304,16 @@ module RenderTestCases
assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david"))
end
+ def test_render_partial_with_object_uses_render_partial_path
+ assert_equal "Hello: lifo",
+ @controller_view.render(:partial => Customer.new("lifo"), :locals => {:greeting => "Hello"})
+ end
+
+ def test_render_partial_with_object_and_format_uses_render_partial_path
+ assert_equal "<greeting>Hello</greeting><name>lifo</name>",
+ @controller_view.render(:partial => Customer.new("lifo"), :formats => :xml, :locals => {:greeting => "Hello"})
+ end
+
def test_render_partial_using_object
assert_equal "Hello: lifo",
@controller_view.render(Customer.new("lifo"), :greeting => "Hello")
diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb
new file mode 100644
index 0000000000..d899d54589
--- /dev/null
+++ b/actionview/test/template/text_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class TextTest < ActiveSupport::TestCase
+ test 'formats returns symbol for recognized MIME type' do
+ assert_equal [:text], ActionView::Template::Text.new('', :text).formats
+ end
+
+ test 'formats returns string for recognized MIME type when MIME does not have symbol' do
+ foo = Mime::Type.lookup("foo")
+ assert_nil foo.to_sym
+ assert_equal ['foo'], ActionView::Template::Text.new('', foo).formats
+ end
+
+ test 'formats returns string for unknown MIME type' do
+ assert_equal ['foo'], ActionView::Template::Text.new('', 'foo').formats
+ end
+end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 269714fad0..c4770840fb 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -53,6 +53,16 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
end
+ def test_raises_missing_translation_message_with_raise_config_option
+ ActionView::Base.raise_on_missing_translations = true
+
+ assert_raise(I18n::MissingTranslationData) do
+ translate("translations.missing")
+ end
+ ensure
+ ActionView::Base.raise_on_missing_translations = false
+ end
+
def test_raises_missing_translation_message_with_raise_option
assert_raise(I18n::MissingTranslationData) do
translate(:"translations.missing", :raise => true)
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index deba33510a..35279a4558 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -53,14 +53,21 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_straight_url
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com")
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
+ end
+
+ def test_button_to_with_path
+ assert_dom_equal(
+ %{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>},
+ button_to("Hello", article_path("Hello".html_safe))
+ )
end
def test_button_to_with_straight_url_and_request_forgery
self.request_forgery = true
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></form>},
button_to("Hello", "http://www.example.com")
)
ensure
@@ -68,102 +75,102 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_form_class
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class')
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class')
end
def test_button_to_with_form_class_escapes
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="&lt;script&gt;evil_js&lt;/script&gt;"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>')
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="&lt;script&gt;evil_js&lt;/script&gt;"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>')
end
def test_button_to_with_query
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
def test_button_to_with_html_safe_URL
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
end
def test_button_to_with_query_and_no_name
- assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&amp;q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="http://www.example.com?q1=v1&amp;q2=v2" /></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2")
end
def test_button_to_with_javascript_confirm
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" })
)
end
def test_button_to_with_javascript_disable_with
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." })
)
end
def test_button_to_with_remote_and_form_options
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" })
)
end
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" })
)
end
def test_button_to_with_remote_and_javascript_disable_with
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." })
)
end
def test_button_to_with_remote_false
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: false)
)
end
def test_button_to_enabled_disabled
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", disabled: false)
)
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input disabled="disabled" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", disabled: true)
)
end
def test_button_to_with_method_delete
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", method: :delete)
)
end
def test_button_to_with_method_get
assert_dom_equal(
- %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="get" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", method: :get)
)
end
def test_button_to_with_block
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><button type="submit"><span>Hello</span></button></form>},
button_to("http://www.example.com") { content_tag(:span, 'Hello') }
)
end
def test_button_to_with_params
assert_dom_equal(
- %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>},
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>},
button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"})
)
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 09fdd84844..0db220ab8f 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,45 +1,7 @@
-* `attribute_changed?` now accepts parameters which check the old and new value of the attribute
+* Introduce `validate` as an alias for `valid?`.
- `model.name_changed?(from: "Pete", to: "Ringo")`
+ This is more intuitive when you want to run validations but don't care about the return value.
- *Tejas Dinkar*
+ *Henrik Nyh*
-* Fix `has_secure_password` to honor bcrypt-ruby's cost attribute.
-
- *T.J. Schuck*
-
-* Updated the `ActiveModel::Dirty#changed_attributes` method to be indifferent between using
- symbols and strings as keys.
-
- *William Myers*
-
-* Added new API methods `reset_changes` and `changes_applied` to `ActiveModel::Dirty`
- that control changes state. Previsously you needed to update internal
- instance variables, but now API methods are available.
-
- *Bogdan Gusiev*
-
-* Fix `has_secure_password` not to trigger `password_confirmation` validations
- if no `password_confirmation` is set.
-
- *Vladimir Kiselev*
-
-* `inclusion` / `exclusion` validations with ranges will only use the faster
- `Range#cover` for numerical ranges, and the more accurate `Range#include?`
- for non-numerical ones.
-
- Fixes range validations like `:a..:f` that used to pass with values like `:be`.
- Fixes #10593.
-
- *Charles Bergeron*
-
-* Fix regression in `has_secure_password`. When a password is set, but a
- confirmation is an empty string, it would incorrectly save.
-
- *Steve Klabnik* and *Phillip Calvin*
-
-* Deprecate `Validator#setup`. This should be done manually now in the validator's constructor.
-
- *Nick Sutterer*
-
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 4c00755532..500be2a04a 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -49,7 +49,8 @@ behavior out of the box:
send("#{attr}=", nil)
end
end
-
+
+ person = Person.new
person.clear_name
person.clear_age
@@ -78,7 +79,21 @@ behavior out of the box:
class Person
include ActiveModel::Dirty
- attr_accessor :name
+ define_attribute_methods :name
+
+ def name
+ @name
+ end
+
+ def name=(val)
+ name_will_change! unless val == @name
+ @name = val
+ end
+
+ def save
+ # do persistence work
+ changes_applied
+ end
end
person = Person.new
@@ -88,6 +103,7 @@ behavior out of the box:
person.changed? # => true
person.changed # => ['name']
person.changes # => { 'name' => [nil, 'bob'] }
+ person.save
person.name = 'robert'
person.save
person.previous_changes # => {'name' => ['bob, 'robert']}
@@ -116,7 +132,10 @@ behavior out of the box:
"Name"
end
end
-
+
+ person = Person.new
+ person.name = nil
+ person.validate!
person.errors.full_messages
# => ["Name cannot be nil"]
@@ -180,41 +199,41 @@ behavior out of the box:
* Validation support
- class Person
- include ActiveModel::Validations
+ class Person
+ include ActiveModel::Validations
- attr_accessor :first_name, :last_name
+ attr_accessor :first_name, :last_name
- validates_each :first_name, :last_name do |record, attr, value|
- record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
- end
- end
+ validates_each :first_name, :last_name do |record, attr, value|
+ record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
+ end
+ end
- person = Person.new
- person.first_name = 'zoolander'
- person.valid? # => false
+ person = Person.new
+ person.first_name = 'zoolander'
+ person.valid? # => false
{Learn more}[link:classes/ActiveModel/Validations.html]
* Custom validators
+
+ class HasNameValidator < ActiveModel::Validator
+ def validate(record)
+ record.errors[:name] = "must exist" if record.name.blank?
+ end
+ end
+
+ class ValidatorPerson
+ include ActiveModel::Validations
+ validates_with HasNameValidator
+ attr_accessor :name
+ end
- class ValidatorPerson
- include ActiveModel::Validations
- validates_with HasNameValidator
- attr_accessor :name
- end
-
- class HasNameValidator < ActiveModel::Validator
- def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
- end
- end
-
- p = ValidatorPerson.new
- p.valid? # => false
- p.errors.full_messages # => ["Name must exist"]
- p.name = "Bob"
- p.valid? # => true
+ p = ValidatorPerson.new
+ p.valid? # => false
+ p.errors.full_messages # => ["Name must exist"]
+ p.name = "Bob"
+ p.valid? # => true
{Learn more}[link:classes/ActiveModel/Validator.html]
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 469f137d03..feb3d9371d 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -48,6 +48,7 @@ module ActiveModel
eager_autoload do
autoload :Errors
+ autoload :StrictValidationFailed, 'active_model/errors'
end
module Serializers
@@ -59,9 +60,9 @@ module ActiveModel
end
end
- def eager_load!
+ def self.eager_load!
super
- ActiveModel::Serializer.eager_load!
+ ActiveModel::Serializers.eager_load!
end
end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 6b0a752566..374265f0d8 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -62,7 +62,7 @@ module ActiveModel
# person = Person.create
# person.to_param # => "1"
def to_param
- persisted? ? to_key.join('-') : nil
+ (persisted? && key = to_key) ? key.join('-') : nil
end
# Returns a +string+ identifying the path associated with the object.
@@ -83,8 +83,8 @@ module ActiveModel
# internal method and should not be accessed directly.
def _to_partial_path #:nodoc:
@_to_partial_path ||= begin
- element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
- collection = ActiveSupport::Inflector.tableize(self)
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
+ collection = ActiveSupport::Inflector.tableize(name)
"#{collection}/#{element}".freeze
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 58d87e6f7f..98ffffeb10 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -136,7 +136,7 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- @previously_changed ||= {}
+ @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Returns a hash of the attributes with unsaved changes indicating their original
@@ -167,13 +167,13 @@ module ActiveModel
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied
@previously_changed = changes
- @changed_attributes = {}
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
# Removes all dirty data: current changes and previous changes
def reset_changes
- @previously_changed = {}
- @changed_attributes = {}
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
# Handle <tt>*_change</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 010c4bb6f9..917d3b9142 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -94,7 +94,7 @@ module ActiveModel
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
def include?(attribute)
- (v = messages[attribute]) && v.any?
+ messages[attribute].present?
end
# aliases include?
alias :has_key? :include?
@@ -289,6 +289,13 @@ module ActiveModel
# # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
+ #
+ # +attribute+ should be set to <tt>:base</tt> if the error is not
+ # directly associated with a single attribute.
+ #
+ # person.errors.add(:base, "either name or email must be present")
+ # person.errors.messages
+ # # => {:base=>["either name or email must be present"]}
def add(attribute, message = :invalid, options = {})
message = normalize_message(attribute, message, options)
if exception = options[:strict]
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
new file mode 100644
index 0000000000..964b24398d
--- /dev/null
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveModel
+ # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index d824a66784..826e89bf9d 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -20,9 +20,9 @@ module ActiveModel
# value to the password_confirmation attribute and the validation
# will not be triggered.
#
- # You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
+ # You need to add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
#
- # gem 'bcrypt-ruby', '~> 3.1.2'
+ # gem 'bcrypt', '~> 3.1.7'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
@@ -42,13 +42,13 @@ module ActiveModel
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
def has_secure_password(options = {})
- # Load bcrypt-ruby only when has_secure_password is used.
+ # Load bcrypt gem only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
begin
require 'bcrypt'
rescue LoadError
- $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
+ $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install"
raise
end
@@ -57,11 +57,15 @@ module ActiveModel
include InstanceMethodsOnActivation
if options.fetch(:validations, true)
- validates_confirmation_of :password, if: :password_confirmation_required?
- validates_presence_of :password, on: :create
- validates_presence_of :password_confirmation, if: :password_confirmation_required?
+ # This ensures the model has a password by checking whether the password_digest
+ # is present, so that this works with both new and existing records. However,
+ # when there is an error, the message is added to the password attribute instead
+ # so that the error message will make sense to the end-user.
+ validate do |record|
+ record.errors.add(:password, :blank) unless record.password_digest.present?
+ end
- before_create { raise "Password digest missing on new record" if password_digest.blank? }
+ validates_confirmation_of :password, if: ->{ password.present? }
end
if respond_to?(:attributes_protected_by_default)
@@ -100,7 +104,9 @@ module ActiveModel
# user.password = 'mUc3m00RsqyRe'
# user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
def password=(unencrypted_password)
- unless unencrypted_password.blank?
+ if unencrypted_password.nil?
+ self.password_digest = nil
+ elsif unencrypted_password.present?
@password = unencrypted_password
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
@@ -110,12 +116,6 @@ module ActiveModel
def password_confirmation=(unencrypted_password)
@password_confirmation = unencrypted_password
end
-
- private
-
- def password_confirmation_required?
- password_confirmation && password.present?
- end
end
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 6be44b5d63..cf97f45dba 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -66,8 +66,10 @@ module ActiveModel
# end
#
# Options:
- # * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # You can pass a symbol or an array of symbols.
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
@@ -124,10 +126,10 @@ module ActiveModel
# end
#
# Options:
- # * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
- # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # You can pass a symbol or an array of symbols.
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
@@ -143,7 +145,7 @@ module ActiveModel
options = options.dup
options[:if] = Array(options[:if])
options[:if].unshift lambda { |o|
- o.validation_context == options[:on]
+ Array(options[:on]).include?(o.validation_context)
}
end
args << options
@@ -199,12 +201,12 @@ module ActiveModel
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
#
- # If one runs Person.clear_validators! and then checks to see what
+ # If one runs <tt>Person.clear_validators!</tt> and then checks to see what
# validators this class has, you would obtain:
#
# Person.validators # => []
#
- # Also, the callback set by +validate :cannot_be_robot+ will be erased
+ # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
# so that:
#
# Person._validate_callbacks.empty? # => true
@@ -283,6 +285,8 @@ module ActiveModel
# Runs all the specified validations and returns +true+ if no errors were
# added otherwise +false+.
#
+ # Aliased as validate.
+ #
# class Person
# include ActiveModel::Validations
#
@@ -317,6 +321,8 @@ module ActiveModel
self.validation_context = current_context
end
+ alias_method :validate, :valid?
+
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
# added, +false+ otherwise.
#
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
index 1a1863370b..9b5416fb1d 100644
--- a/activemodel/lib/active_model/validations/absence.rb
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -21,7 +21,7 @@ module ActiveModel
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_absence_of(*attr_names)
validates_with AbsenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 139de16326..ac5e79859b 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -38,8 +38,6 @@ module ActiveModel
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
- # is +true+).
# * <tt>:accept</tt> - Specifies value that is considered accepted.
# The default value is a string "1", which makes it easy to relate to
# an HTML checkbox. This should be set to +true+ if you are validating
@@ -47,8 +45,8 @@ module ActiveModel
# before validation.
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
+ # See <tt>ActiveModel::Validation#validates</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index b0542661af..a51523912f 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -57,7 +57,7 @@ module ActiveModel
# confirmation").
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 48bf5cd802..f342d27275 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -34,13 +34,9 @@ module ActiveModel
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
- # attribute is blank(default is +false+).
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index f0fe22438f..ff3e95da34 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -91,10 +91,6 @@ module ActiveModel
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
- # attribute is blank (default is +false+).
# * <tt>:with</tt> - Regular expression that if the attribute matches will
# result in a successful validation. This can be provided as a proc or
# lambda returning regular expression which will be called at runtime.
@@ -107,7 +103,7 @@ module ActiveModel
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index b344095807..c84025f083 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -34,13 +34,9 @@ module ActiveModel
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# not included in the list").
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
- # attribute is blank (default is +false+).
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index c8d3236463..a9fb9804d4 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -110,7 +110,7 @@ module ActiveModel
# * <tt>:even</tt> - Specifies the value must be an even number.
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+ .
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
# See <tt>ActiveModel::Validation#validates</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index ab8c8359fc..5d593274eb 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -29,7 +29,7 @@ module ActiveModel
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index bf588b7bd0..ae8d377fdf 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -83,7 +83,9 @@ module ActiveModel
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or
# +false+ value.
- # * <tt>:strict</tt> - if the <tt>:strict</tt> option is set to true
+ # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
+ # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 16bd6670d1..ff41572105 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -53,7 +53,7 @@ module ActiveModel
#
# Configuration options:
# * <tt>:on</tt> - Specifies when this validation is active
- # (<tt>:create</tt> or <tt>:update</tt>.
+ # (<tt>:create</tt> or <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
@@ -139,6 +139,8 @@ module ActiveModel
# class version of this method for more information.
def validates_with(*args, &block)
options = args.extract_options!
+ options[:class] = self.class
+
args.each do |klass|
validator = klass.new(options, &block)
validator.validate(self)
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 58ba3ab9b2..b1f9082ea7 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveModel
- # Returns the version of the currently loaded ActiveModel as a Gem::Version
+ # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments
- STRING = ActiveModel.version.to_s
+ gem_version
end
end
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index e9cb5ccc96..e81b7ac424 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -104,10 +104,14 @@ class AttributeMethodsTest < ActiveModel::TestCase
end
test '#define_attribute_method generates attribute method' do
- ModelWithAttributes.define_attribute_method(:foo)
+ begin
+ ModelWithAttributes.define_attribute_method(:foo)
- assert_respond_to ModelWithAttributes.new, :foo
- assert_equal "value of foo", ModelWithAttributes.new.foo
+ assert_respond_to ModelWithAttributes.new, :foo
+ assert_equal "value of foo", ModelWithAttributes.new.foo
+ ensure
+ ModelWithAttributes.undefine_attribute_methods
+ end
end
test '#define_attribute_method does not generate attribute method if already defined in attribute module' do
@@ -134,24 +138,36 @@ class AttributeMethodsTest < ActiveModel::TestCase
end
test '#define_attribute_method generates attribute method with invalid identifier characters' do
- ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
+ begin
+ ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
- assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b'
- assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
+ assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b'
+ assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
+ ensure
+ ModelWithWeirdNamesAttributes.undefine_attribute_methods
+ end
end
test '#define_attribute_methods works passing multiple arguments' do
- ModelWithAttributes.define_attribute_methods(:foo, :baz)
+ begin
+ ModelWithAttributes.define_attribute_methods(:foo, :baz)
- assert_equal "value of foo", ModelWithAttributes.new.foo
- assert_equal "value of baz", ModelWithAttributes.new.baz
+ assert_equal "value of foo", ModelWithAttributes.new.foo
+ assert_equal "value of baz", ModelWithAttributes.new.baz
+ ensure
+ ModelWithAttributes.undefine_attribute_methods
+ end
end
test '#define_attribute_methods generates attribute methods' do
- ModelWithAttributes.define_attribute_methods(:foo)
+ begin
+ ModelWithAttributes.define_attribute_methods(:foo)
- assert_respond_to ModelWithAttributes.new, :foo
- assert_equal "value of foo", ModelWithAttributes.new.foo
+ assert_respond_to ModelWithAttributes.new, :foo
+ assert_equal "value of foo", ModelWithAttributes.new.foo
+ ensure
+ ModelWithAttributes.undefine_attribute_methods
+ end
end
test '#alias_attribute generates attribute_aliases lookup hash' do
@@ -164,26 +180,38 @@ class AttributeMethodsTest < ActiveModel::TestCase
end
test '#define_attribute_methods generates attribute methods with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
+ begin
+ ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
- assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar'
- assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar')
+ assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar'
+ assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar')
+ ensure
+ ModelWithAttributesWithSpaces.undefine_attribute_methods
+ end
end
test '#alias_attribute works with attributes with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
- ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar')
+ begin
+ ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
+ ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar')
- assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
+ assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
+ ensure
+ ModelWithAttributesWithSpaces.undefine_attribute_methods
+ end
end
test '#alias_attribute works with attributes named as a ruby keyword' do
- ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end])
- ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin)
- ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end)
-
- assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from
- assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
+ begin
+ ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end])
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin)
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end)
+
+ assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from
+ assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
+ ensure
+ ModelWithRubyKeywordNamedAttributes.undefine_attribute_methods
+ end
end
test '#undefine_attribute_methods removes attribute methods' do
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index 3bb177591d..c5cfbf909d 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -24,6 +24,16 @@ class ConversionTest < ActiveModel::TestCase
assert_equal "1", Contact.new(id: 1).to_param
end
+ test "to_param returns nil if to_key is nil" do
+ klass = Class.new(Contact) do
+ def persisted?
+ true
+ end
+ end
+
+ assert_nil klass.new.to_param
+ end
+
test "to_partial_path default implementation returns a string giving a relative path" do
assert_equal "contacts/contact", Contact.new.to_partial_path
assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path,
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 54427a1513..2853476c91 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -41,6 +41,10 @@ class DirtyTest < ActiveModel::TestCase
def save
changes_applied
end
+
+ def reload
+ reset_changes
+ end
end
setup do
@@ -83,6 +87,14 @@ class DirtyTest < ActiveModel::TestCase
assert_not_nil @model.changes['name']
end
+ test "be consistent with symbols arguments after the changes are applied" do
+ @model.name = "David"
+ assert @model.attribute_changed?(:name)
+ @model.save
+ @model.name = 'Rafael'
+ assert @model.attribute_changed?(:name)
+ end
+
test "attribute mutation" do
@model.instance_variable_set("@name", "Yam")
assert !@model.name_changed?
@@ -149,4 +161,19 @@ class DirtyTest < ActiveModel::TestCase
@model.size = 1
assert @model.size_changed?
end
+
+ test "reload should reset all changes" 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']
+
+ @model.reload
+
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
+ end
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index bbd186d83d..42d0365521 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -51,7 +51,12 @@ class ErrorsTest < ActiveModel::TestCase
def test_has_key?
errors = ActiveModel::Errors.new(self)
errors[:foo] = 'omg'
- assert errors.has_key?(:foo), 'errors should have key :foo'
+ assert_equal true, errors.has_key?(:foo), 'errors should have key :foo'
+ end
+
+ def test_has_no_key
+ errors = ActiveModel::Errors.new(self)
+ assert_equal false, errors.has_key?(:name), 'errors should not have key :name'
end
test "clear errors" do
@@ -77,6 +82,13 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal({ foo: "omg" }, errors.messages)
end
+ test "error access is indifferent" do
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = "omg"
+
+ assert_equal ["omg"], errors["foo"]
+ end
+
test "values returns an array of messages" do
errors = ActiveModel::Errors.new(self)
errors.set(:foo, "omg")
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 0314803af6..bcd1e04a0f 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -1,118 +1,187 @@
require 'cases/helper'
require 'models/user'
-require 'models/oauthed_user'
require 'models/visitor'
class SecurePasswordTest < ActiveModel::TestCase
setup do
+ # Used only to speed up tests
+ @original_min_cost = ActiveModel::SecurePassword.min_cost
ActiveModel::SecurePassword.min_cost = true
@user = User.new
@visitor = Visitor.new
- @oauthed_user = OauthedUser.new
+
+ # Simulate loading an existing user from the DB
+ @existing_user = User.new
+ @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST)
end
teardown do
- ActiveModel::SecurePassword.min_cost = false
+ ActiveModel::SecurePassword.min_cost = @original_min_cost
end
- test "blank password" do
- @user.password = @visitor.password = ''
- assert !@user.valid?(:create), 'user should be invalid'
+ test "create and updating without validations" do
assert @visitor.valid?(:create), 'visitor should be valid'
- end
+ assert @visitor.valid?(:update), 'visitor should be valid'
+
+ @visitor.password = '123'
+ @visitor.password_confirmation = '456'
- test "nil password" do
- @user.password = @visitor.password = nil
- assert !@user.valid?(:create), 'user should be invalid'
assert @visitor.valid?(:create), 'visitor should be valid'
+ assert @visitor.valid?(:update), 'visitor should be valid'
end
- test "blank password doesn't override previous password" do
- @user.password = 'test'
+ test "create a new user with validation and a blank password" do
@user.password = ''
- assert_equal @user.password, 'test'
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["can't be blank"], @user.errors[:password]
end
- test "password must be present" do
- assert !@user.valid?(:create)
- assert_equal 1, @user.errors.size
+ test "create a new user with validation and a nil password" do
+ @user.password = nil
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["can't be blank"], @user.errors[:password]
end
- test "match confirmation" do
- @user.password = @visitor.password = "thiswillberight"
- @user.password_confirmation = @visitor.password_confirmation = "wrong"
+ test "create a new user with validation and a blank password confirmation" do
+ @user.password = 'password'
+ @user.password_confirmation = ''
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
+ end
- assert !@user.valid?
- assert @visitor.valid?
+ test "create a new user with validation and a nil password confirmation" do
+ @user.password = 'password'
+ @user.password_confirmation = nil
+ assert @user.valid?(:create), 'user should be valid'
+ end
- @user.password_confirmation = "thiswillberight"
+ test "create a new user with validation and an incorrect password confirmation" do
+ @user.password = 'password'
+ @user.password_confirmation = 'something else'
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
+ end
- assert @user.valid?
+ test "create a new user with validation and a correct password confirmation" do
+ @user.password = 'password'
+ @user.password_confirmation = 'something else'
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end
- test "authenticate" do
- @user.password = "secret"
+ test "update an existing user with validation and no change in password" do
+ assert @existing_user.valid?(:update), 'user should be valid'
+ end
- assert !@user.authenticate("wrong")
- assert @user.authenticate("secret")
+ test "updating an existing user with validation and a blank password" do
+ @existing_user.password = ''
+ assert @existing_user.valid?(:update), 'user should be valid'
end
- test "User should not be created with blank digest" do
- assert_raise RuntimeError do
- @user.run_callbacks :create
- end
- @user.password = "supersecretpassword"
- assert_nothing_raised do
- @user.run_callbacks :create
- end
+ test "updating an existing user with validation and a blank password and password_confirmation" do
+ @existing_user.password = ''
+ @existing_user.password_confirmation = ''
+ assert @existing_user.valid?(:update), 'user should be valid'
end
- test "Oauthed user can be created with blank digest" do
- assert_nothing_raised do
- @oauthed_user.run_callbacks :create
- end
+ test "updating an existing user with validation and a nil password" do
+ @existing_user.password = nil
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["can't be blank"], @existing_user.errors[:password]
end
- test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
- ActiveModel::SecurePassword.min_cost = false
+ test "updating an existing user with validation and a blank password confirmation" do
+ @existing_user.password = 'password'
+ @existing_user.password_confirmation = ''
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
+ end
- @user.password = "secret"
- assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
+ test "updating an existing user with validation and a nil password confirmation" do
+ @existing_user.password = 'password'
+ @existing_user.password_confirmation = nil
+ assert @existing_user.valid?(:update), 'user should be valid'
end
- test "Password digest cost honors bcrypt cost attribute when min_cost is false" do
- ActiveModel::SecurePassword.min_cost = false
- BCrypt::Engine.cost = 5
+ test "updating an existing user with validation and an incorrect password confirmation" do
+ @existing_user.password = 'password'
+ @existing_user.password_confirmation = 'something else'
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
+ end
- @user.password = "secret"
- assert_equal BCrypt::Engine.cost, @user.password_digest.cost
+ test "updating an existing user with validation and a correct password confirmation" do
+ @existing_user.password = 'password'
+ @existing_user.password_confirmation = 'something else'
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end
- test "Password digest cost can be set to bcrypt min cost to speed up tests" do
- ActiveModel::SecurePassword.min_cost = true
+ test "updating an existing user with validation and a blank password digest" do
+ @existing_user.password_digest = ''
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["can't be blank"], @existing_user.errors[:password]
+ end
+
+ test "updating an existing user with validation and a nil password digest" do
+ @existing_user.password_digest = nil
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["can't be blank"], @existing_user.errors[:password]
+ end
+
+ test "setting a blank password should not change an existing password" do
+ @existing_user.password = ''
+ assert @existing_user.password_digest == 'password'
+ end
+ test "setting a nil password should clear an existing password" do
+ @existing_user.password = nil
+ assert_equal nil, @existing_user.password_digest
+ end
+
+ test "authenticate" do
@user.password = "secret"
- assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
+
+ assert !@user.authenticate("wrong")
+ assert @user.authenticate("secret")
end
- test "blank password_confirmation does not result in a confirmation error" do
- @user.password = ""
- @user.password_confirmation = ""
- assert @user.valid?(:update), "user should be valid"
+ test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
+ ActiveModel::SecurePassword.min_cost = false
+
+ @user.password = "secret"
+ assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
end
- test "password_confirmation validations will not be triggered if password_confirmation is not sent" do
- @user.password = "password"
- assert @user.valid?(:create)
+ test "Password digest cost honors bcrypt cost attribute when min_cost is false" do
+ begin
+ original_bcrypt_cost = BCrypt::Engine.cost
+ ActiveModel::SecurePassword.min_cost = false
+ BCrypt::Engine.cost = 5
+
+ @user.password = "secret"
+ assert_equal BCrypt::Engine.cost, @user.password_digest.cost
+ ensure
+ BCrypt::Engine.cost = original_bcrypt_cost
+ end
end
- test "will not save if confirmation is blank but password is not" do
- @user.password = "password"
- @user.password_confirmation = ""
- assert_not @user.valid?(:create)
+ test "Password digest cost can be set to bcrypt min cost to speed up tests" do
+ ActiveModel::SecurePassword.min_cost = true
- @user.password_confirmation = "password"
- assert @user.valid?(:create)
+ @user.password = "secret"
+ assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
end
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index bc185c737f..60414a6570 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -30,11 +30,6 @@ class JsonSerializationTest < ActiveModel::TestCase
@contact.preferences = { 'shows' => 'anime' }
end
- def teardown
- # set to the default value
- Contact.include_root_in_json = false
- end
-
test "should not include root in json (class method)" do
json = @contact.to_json
@@ -47,19 +42,25 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "should include root in json if include_root_in_json is true" do
- Contact.include_root_in_json = true
- json = @contact.to_json
-
- assert_match %r{^\{"contact":\{}, json
- assert_match %r{"name":"Konata Izumi"}, json
- assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
- assert_match %r{"awesome":true}, json
- assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ begin
+ original_include_root_in_json = Contact.include_root_in_json
+ Contact.include_root_in_json = true
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ ensure
+ Contact.include_root_in_json = original_include_root_in_json
+ end
end
test "should include root in json (option) even if the default is set to false" do
json = @contact.to_json(root: true)
+
assert_match %r{^\{"contact":\{}, json
end
@@ -145,13 +146,18 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "as_json should return a hash if include_root_in_json is true" do
- Contact.include_root_in_json = true
- json = @contact.as_json
-
- assert_kind_of Hash, json
- assert_kind_of Hash, json['contact']
- %w(name age created_at awesome preferences).each do |field|
- assert_equal @contact.send(field), json['contact'][field]
+ begin
+ original_include_root_in_json = Contact.include_root_in_json
+ Contact.include_root_in_json = true
+ json = @contact.as_json
+
+ assert_kind_of Hash, json
+ assert_kind_of Hash, json['contact']
+ %w(name age created_at awesome preferences).each do |field|
+ assert_equal @contact.send(field), json['contact'][field]
+ end
+ ensure
+ Contact.include_root_in_json = original_include_root_in_json
end
end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 11ee17bb27..5db14c8157 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -57,48 +57,48 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize default root" do
- @xml = @contact.to_xml
- assert_match %r{^<contact>}, @xml
- assert_match %r{</contact>$}, @xml
+ xml = @contact.to_xml
+ assert_match %r{^<contact>}, xml
+ assert_match %r{</contact>$}, xml
end
test "should serialize namespaced root" do
- @xml = Admin::Contact.new(@contact.attributes).to_xml
- assert_match %r{^<contact>}, @xml
- assert_match %r{</contact>$}, @xml
+ xml = Admin::Contact.new(@contact.attributes).to_xml
+ assert_match %r{^<contact>}, xml
+ assert_match %r{</contact>$}, xml
end
test "should serialize default root with namespace" do
- @xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml
- assert_match %r{</contact>$}, @xml
+ xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
+ assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, xml
+ assert_match %r{</contact>$}, xml
end
test "should serialize custom root" do
- @xml = @contact.to_xml root: 'xml_contact'
- assert_match %r{^<xml-contact>}, @xml
- assert_match %r{</xml-contact>$}, @xml
+ xml = @contact.to_xml root: 'xml_contact'
+ assert_match %r{^<xml-contact>}, xml
+ assert_match %r{</xml-contact>$}, xml
end
test "should allow undasherized tags" do
- @xml = @contact.to_xml root: 'xml_contact', dasherize: false
- assert_match %r{^<xml_contact>}, @xml
- assert_match %r{</xml_contact>$}, @xml
- assert_match %r{<created_at}, @xml
+ xml = @contact.to_xml root: 'xml_contact', dasherize: false
+ assert_match %r{^<xml_contact>}, xml
+ assert_match %r{</xml_contact>$}, xml
+ assert_match %r{<created_at}, xml
end
test "should allow camelized tags" do
- @xml = @contact.to_xml root: 'xml_contact', camelize: true
- assert_match %r{^<XmlContact>}, @xml
- assert_match %r{</XmlContact>$}, @xml
- assert_match %r{<CreatedAt}, @xml
+ xml = @contact.to_xml root: 'xml_contact', camelize: true
+ assert_match %r{^<XmlContact>}, xml
+ assert_match %r{</XmlContact>$}, xml
+ assert_match %r{<CreatedAt}, xml
end
test "should allow lower-camelized tags" do
- @xml = @contact.to_xml root: 'xml_contact', camelize: :lower
- assert_match %r{^<xmlContact>}, @xml
- assert_match %r{</xmlContact>$}, @xml
- assert_match %r{<createdAt}, @xml
+ xml = @contact.to_xml root: 'xml_contact', camelize: :lower
+ assert_match %r{^<xmlContact>}, xml
+ assert_match %r{</xmlContact>$}, xml
+ assert_match %r{<createdAt}, xml
end
test "should use serializable hash" do
@@ -106,22 +106,22 @@ class XmlSerializationTest < ActiveModel::TestCase
@contact.name = 'aaron stack'
@contact.age = 25
- @xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, @xml
- assert_match %r{<age type="integer">25</age>}, @xml
- assert_no_match %r{<awesome>}, @xml
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_match %r{<age type="integer">25</age>}, xml
+ assert_no_match %r{<awesome>}, xml
end
test "should allow skipped types" do
- @xml = @contact.to_xml skip_types: true
- assert_match %r{<age>25</age>}, @xml
+ xml = @contact.to_xml skip_types: true
+ assert_match %r{<age>25</age>}, xml
end
test "should include yielded additions" do
- @xml = @contact.to_xml do |xml|
+ xml_output = @contact.to_xml do |xml|
xml.creator "David"
end
- assert_match %r{<creator>David</creator>}, @xml
+ assert_match %r{<creator>David</creator>}, xml_output
end
test "should serialize string" do
@@ -162,7 +162,7 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<nationality>unknown</nationality>}, xml
end
- test 'should supply serializable to second proc argument' do
+ test "should supply serializable to second proc argument" do
proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
xml = @contact.to_xml(procs: [ proc ])
assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index deb4e1ed0a..cedc812ec7 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -7,6 +7,10 @@ class ActiveModelI18nTests < ActiveModel::TestCase
I18n.backend = I18n::Backend::Simple.new
end
+ def teardown
+ I18n.backend.reload!
+ end
+
def test_translated_model_attributes
I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute' } } }
assert_equal 'person name attribute', Person.human_attribute_name('name')
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index c05d71de5a..795ce16d28 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -6,9 +6,9 @@ require 'models/custom_reader'
class AbsenceValidationTest < ActiveModel::TestCase
teardown do
- Topic.reset_callbacks(:validate)
- Person.reset_callbacks(:validate)
- CustomReader.reset_callbacks(:validate)
+ Topic.clear_validators!
+ Person.clear_validators!
+ CustomReader.clear_validators!
end
def test_validate_absences
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index dc413bef30..e78aa1adaf 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -8,7 +8,7 @@ require 'models/person'
class AcceptanceValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_terms_of_service_agreement_no_acceptance
@@ -63,6 +63,6 @@ class AcceptanceValidationTest < ActiveModel::TestCase
p.karma = "1"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
end
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 5049d6dd61..1261937b56 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -6,7 +6,7 @@ require 'models/topic'
class ConditionalValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_if_validation_using_method_true
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index f03de2c24a..65a2a1eb49 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -7,7 +7,7 @@ require 'models/person'
class ConfirmationValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_no_title_confirmation
@@ -49,26 +49,29 @@ class ConfirmationValidationTest < ActiveModel::TestCase
p.karma = "None"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
def test_title_confirmation_with_i18n_attribute
- @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
- I18n.load_path.clear
- I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations('en', {
- errors: { messages: { confirmation: "doesn't match %{attribute}" } },
- activemodel: { attributes: { topic: { title: 'Test Title'} } }
- })
-
- Topic.validates_confirmation_of(:title)
-
- t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
- assert t.invalid?
- assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
-
- I18n.load_path.replace @old_load_path
- I18n.backend = @old_backend
+ begin
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', {
+ errors: { messages: { confirmation: "doesn't match %{attribute}" } },
+ activemodel: { attributes: { topic: { title: 'Test Title'} } }
+ })
+
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ assert t.invalid?
+ assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
+ ensure
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ I18n.backend.reload!
+ end
end
test "does not override confirmation reader if present" do
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 81455ba519..1ce41f9bc9 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -7,7 +7,7 @@ require 'models/person'
class ExclusionValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_validates_exclusion_of
@@ -50,7 +50,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
p.karma = "Lifo"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
def test_validates_exclusion_of_with_lambda
@@ -87,6 +87,6 @@ class ExclusionValidationTest < ActiveModel::TestCase
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 26e8dbf19c..0f91b73cd7 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -7,7 +7,7 @@ require 'models/person'
class PresenceValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_validate_format
@@ -68,11 +68,11 @@ class PresenceValidationTest < ActiveModel::TestCase
assert t.invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
end
-
+
def test_validate_format_of_with_multiline_regexp_should_raise_error
assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /^Valid Title$/) }
end
-
+
def test_validate_format_of_with_multiline_regexp_and_option
assert_nothing_raised(ArgumentError) do
Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true)
@@ -144,6 +144,6 @@ class PresenceValidationTest < ActiveModel::TestCase
p.karma = "1234"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 40a5aee997..93600c587a 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -4,7 +4,7 @@ require 'models/person'
class I18nGenerateMessageValidationTest < ActiveModel::TestCase
def setup
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
@person = Person.new
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index e29771d6b7..96084a32ba 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -6,7 +6,7 @@ require 'models/person'
class I18nValidationTest < ActiveModel::TestCase
def setup
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
@person = Person.new
@old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
@@ -16,9 +16,10 @@ class I18nValidationTest < ActiveModel::TestCase
end
def teardown
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
+ I18n.backend.reload!
end
def test_full_message_encoding
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 8b90856869..3a8f3080e1 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -8,7 +8,7 @@ require 'models/person'
class InclusionValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_validates_inclusion_of_range
@@ -105,7 +105,7 @@ class InclusionValidationTest < ActiveModel::TestCase
p.karma = "monkey"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
def test_validates_inclusion_of_with_lambda
@@ -142,6 +142,6 @@ class InclusionValidationTest < ActiveModel::TestCase
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 8b2f886cc4..046ffcb16f 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -6,7 +6,7 @@ require 'models/person'
class LengthValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_validates_length_of_with_allow_nil
@@ -354,7 +354,7 @@ class LengthValidationTest < ActiveModel::TestCase
p.karma = "The Smiths"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
def test_validates_length_of_for_infinite_maxima
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 84332ed014..e1657407cf 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -9,7 +9,7 @@ require 'bigdecimal'
class NumericalityValidationTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
NIL = [nil]
@@ -119,6 +119,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
invalid!([3, 4])
valid!([5, 6])
+ ensure
Topic.send(:remove_method, :min_approved)
end
@@ -128,6 +129,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
invalid!([6])
valid!([4, 5])
+ ensure
Topic.send(:remove_method, :max_approved)
end
@@ -157,7 +159,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
p.karma = "1234"
assert p.valid?
ensure
- Person.reset_callbacks(:validate)
+ Person.clear_validators!
end
def test_validates_numericality_with_invalid_args
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index 2f228cfa83..ecf16d1e16 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -8,9 +8,9 @@ require 'models/custom_reader'
class PresenceValidationTest < ActiveModel::TestCase
teardown do
- Topic.reset_callbacks(:validate)
- Person.reset_callbacks(:validate)
- CustomReader.reset_callbacks(:validate)
+ Topic.clear_validators!
+ Person.clear_validators!
+ CustomReader.clear_validators!
end
def test_validate_presences
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index c1914b32bc..699a872e42 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -11,9 +11,9 @@ class ValidatesTest < ActiveModel::TestCase
teardown :reset_callbacks
def reset_callbacks
- Person.reset_callbacks(:validate)
- Topic.reset_callbacks(:validate)
- PersonWithValidator.reset_callbacks(:validate)
+ Person.clear_validators!
+ Topic.clear_validators!
+ PersonWithValidator.clear_validators!
end
def test_validates_with_messages_empty
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
index 5f99b320a6..005bf118c6 100644
--- a/activemodel/test/cases/validations/validations_context_test.rb
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -4,10 +4,8 @@ require 'cases/helper'
require 'models/topic'
class ValidationsContextTest < ActiveModel::TestCase
-
def teardown
- Topic.reset_callbacks(:validate)
- Topic._validators.clear
+ Topic.clear_validators!
end
ERROR_MESSAGE = "Validation error from validator"
@@ -36,4 +34,17 @@ class ValidationsContextTest < ActiveModel::TestCase
assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create"
assert topic.errors[:base].include?(ERROR_MESSAGE)
end
+
+ test "with a class that adds errors on multiple contexts and validating a new model" do
+ Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2])
+
+ topic = Topic.new
+ assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2"
+
+ assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+
+ assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 93716f1433..736c2deea8 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -6,8 +6,7 @@ require 'models/topic'
class ValidatesWithTest < ActiveModel::TestCase
def teardown
- Topic.reset_callbacks(:validate)
- Topic._validators.clear
+ Topic.clear_validators!
end
ERROR_MESSAGE = "Validation error from validator"
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 039b6b8872..6a74ee353d 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -10,17 +10,10 @@ require 'active_support/json'
require 'active_support/xml_mini'
class ValidationsTest < ActiveModel::TestCase
-
class CustomStrictValidationException < StandardError; end
- def setup
- Topic._validators.clear
- end
-
- # Most of the tests mess with the validations of Topic, so lets repair it all the time.
- # Other classes we mess with will be dealt with in the specific tests
def teardown
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
end
def test_single_field_validation
@@ -146,6 +139,8 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
+ ensure
+ CustomReader.clear_validators!
end
def test_validate_block
@@ -291,14 +286,24 @@ class ValidationsTest < ActiveModel::TestCase
auto = Automobile.new
assert auto.invalid?
- assert_equal 2, auto.errors.size
+ assert_equal 3, auto.errors.size
auto.make = 'Toyota'
auto.model = 'Corolla'
+ auto.approved = '1'
assert auto.valid?
end
+ def test_validate
+ auto = Automobile.new
+
+ assert_empty auto.errors
+
+ auto.validate
+ assert_not_empty auto.errors
+ end
+
def test_strict_validation_in_validates
Topic.validates :title, strict: true, presence: true
assert_raises ActiveModel::StrictValidationFailed do
diff --git a/activemodel/test/models/automobile.rb b/activemodel/test/models/automobile.rb
index ece644c40c..4df2fe8b3a 100644
--- a/activemodel/test/models/automobile.rb
+++ b/activemodel/test/models/automobile.rb
@@ -3,10 +3,11 @@ class Automobile
validate :validations
- attr_accessor :make, :model
+ attr_accessor :make, :model, :approved
def validations
validates_presence_of :make
validates_length_of :model, within: 2..10
+ validates_acceptance_of :approved, allow_nil: false
end
end
diff --git a/activemodel/test/models/oauthed_user.rb b/activemodel/test/models/oauthed_user.rb
deleted file mode 100644
index 9750bc19d4..0000000000
--- a/activemodel/test/models/oauthed_user.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class OauthedUser
- extend ActiveModel::Callbacks
- include ActiveModel::Validations
- include ActiveModel::SecurePassword
-
- define_model_callbacks :create
-
- has_secure_password(validations: false)
-
- attr_accessor :password_digest, :password_salt
-end
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index 4b11df12bf..cbe259b1ad 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -7,5 +7,5 @@ class User
has_secure_password
- attr_accessor :password_digest, :password_salt
+ attr_accessor :password_digest
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 27ad5a2f84..4bbe32f948 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -2,1548 +2,421 @@
Fixes: #13631
- *Timur Alperovich*
+ *Timur Alperovich*
-* Enable partial indexes for sqlite >= 3.8.0
+* Give ActiveRecord::PredicateBuilder private methods the privacy they deserve
- See http://www.sqlite.org/partialindex.html
+ *Hector Satre*
- *Cody Cutrer*
+* 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.
-* Don't try to get the subclass if the inheritance column doesn't exist
+ Fixes #14845.
- The `subclass_from_attrs` method is called even if the column specified by
- the `inheritance_column` setting doesn't exist. This prevents setting associations
- via the attributes hash if the association name clashes with the value of the setting,
- typically `:type`. This worked previously in Rails 3.2.
-
- *Ujjwal Thaakar*
-
-* Enum mappings are now exposed via class methods instead of constants.
-
- Example:
-
- class Conversation < ActiveRecord::Base
- enum status: [ :active, :archived ]
- end
-
- Before:
-
- Conversation::STATUS # => { "active" => 0, "archived" => 1 }
-
- After:
-
- Conversation.statuses # => { "active" => 0, "archived" => 1 }
-
- *Godfrey Chan*
-
-* Set `NameError#name` when STI-class-lookup fails.
-
- *Chulki Lee*
-
-* Fix bug in `becomes!` when changing from the base model to a STI sub-class.
-
- Fixes #13272.
-
- *the-web-dev*, *Yves Senn*
-
-* Currently Active Record can be configured via the environment variable
- `DATABASE_URL` or by manually injecting a hash of values which is what Rails does,
- reading in `database.yml` and setting Active Record appropriately. Active Record
- expects to be able to use `DATABASE_URL` without the use of Rails, and we cannot
- rip out this functionality without deprecating. This presents a problem though
- when both config is set, and a `DATABASE_URL` is present. Currently the
- `DATABASE_URL` should "win" and none of the values in `database.yml` are
- used. This is somewhat unexpected, if one were to set values such as
- `pool` in the `production:` group of `database.yml` they are ignored.
-
- There are many ways that Active Record initiates a connection today:
-
- - Stand Alone (without rails)
- - `rake db:<tasks>`
- - `ActiveRecord.establish_connection`
-
- - With Rails
- - `rake db:<tasks>`
- - `rails <server> | <console>`
- - `rails dbconsole`
-
- Now all of these behave exactly the same way. The best way to do
- this is to put all of this logic in one place so it is guaranteed to be used.
-
- Here is the matrix of how this behavior works:
-
- ```
- No database.yml
- No DATABASE_URL
- => Error
- ```
-
- ```
- database.yml present
- No DATABASE_URL
- => Use database.yml configuration
- ```
-
- ```
- No database.yml
- DATABASE_URL present
- => use DATABASE_URL configuration
- ```
-
- ```
- database.yml present
- DATABASE_URL present
- => Merged into `url` sub key. If both specify `url` sub key, the `database.yml` `url`
- sub key "wins". If other paramaters `adapter` or `database` are specified in YAML,
- they are discarded as the `url` sub key "wins".
- ```
-
- Current implementation uses `ActiveRecord::Base.configurations` to resolve and merge
- all connection information before returning. This is achieved through a utility
- class: `ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig`.
-
- To understand the exact behavior of this class, it is best to review the
- behavior in `activerecord/test/cases/connection_adapters/connection_handler_test.rb`.
-
- *Richard Schneeman*
-
-* Make `change_column_null` revertable. Fixes #13576.
-
- *Yves Senn*, *Nishant Modak*, *Prathamesh Sonpatki*
-
-* Don't create/drop the test database if RAILS_ENV is specified explicitly.
-
- Previously, when the environment was development, we would always
- create or drop both the test and development databases.
-
- Now, if RAILS_ENV is explicitly defined as development, we don't create
- the test database.
-
- *Damien Mathieu*
-
-* Initialize version on Migration objects so that it can be used in a migration,
- and it will be included in the announce message.
-
- *Dylan Thacker-Smith*
-
-* `change_table` now uses the current adapter's `update_table_definition`
- method to retrieve a specific table definition.
- This ensures that `change_table` and `create_table` will use
- similar objects.
-
- Fixes #13577 and #13503.
-
- *Nishant Modak*, *Prathamesh Sonpatki*, *Rafael Mendonça França*
-
-* Fixed ActiveRecord::Store nil conversion TypeError when using YAML coder.
- In case the YAML passed as paramter is nil, uses an empty string.
-
- Fixes #13570.
-
- *Thales Oliveira*
+ *Kassio Borges*
-* Deprecate unused `ActiveRecord::Base.symbolized_base_class`
- and `ActiveRecord::Base.symbolized_sti_name` without replacement.
+* Reset the cache when modifying a Relation with cached Arel.
+ Additionally display a warning message to make the user aware.
*Yves Senn*
-* Since the `test_help.rb` file in Railties now automatically maintains
- your test schema, the `rake db:test:*` tasks are deprecated. This
- doesn't stop you manually running other tasks on your test database
- if needed:
-
- rake db:schema:load RAILS_ENV=test
-
- *Jon Leighton*
-
-* Fix presence validator for association when the associated record responds to `to_a`.
-
- *gmarik*
-
-* Fixed regression on preload/includes with multiple arguments failing in certain conditions,
- raising a NoMethodError internally by calling `reflect_on_association` for `NilClass:Class`.
-
- Fixes #13437.
-
- *Vipul A M*, *khustochka*
-
-* Add the ability to nullify the `enum` column.
-
- Example:
-
- class Conversation < ActiveRecord::Base
- enum gender: [:female, :male]
- end
-
- Conversation::GENDER # => { female: 0, male: 1 }
-
- # conversation.update! gender: 0
- conversation.female!
- conversation.female? # => true
- conversation.gender # => "female"
-
- # conversation.update! gender: nil
- conversation.gender = nil
- conversation.gender.nil? # => true
- conversation.gender # => nil
-
- *Amr Tamimi*
-
-* Connection specification now accepts a "url" key. The value of this
- key is expected to contain a database URL. The database URL will be
- expanded into a hash and merged.
-
- *Richard Schneeman*
-
-* An `ArgumentError` is now raised on a call to `Relation#where.not(nil)`.
-
- Example:
-
- User.where.not(nil)
-
- # Before
- # => 'SELECT `users`.* FROM `users` WHERE (NOT (NULL))'
-
- # After
- # => ArgumentError, 'Invalid argument for .where.not(), got nil.'
-
- *Kuldeep Aggarwal*
-
-* Deprecated use of string argument as a configuration lookup in
- `ActiveRecord::Base.establish_connection`. Instead, a symbol must be given.
-
- *José Valim*
-
-* Fixed `update_column`, `update_columns`, and `update_all` to correctly serialize
- values for `array`, `hstore` and `json` column types in PostgreSQL.
-
- Fixes #12261.
-
- *Tadas Tamosauskas*, *Carlos Antonio da Silva*
-
-* Do not consider PostgreSQL array columns as number or text columns.
-
- The code uses these checks in several places to know what to do with a
- particular column, for instance AR attribute query methods has a branch
- like this:
-
- if column.number?
- !value.zero?
- end
-
- This should never be true for array columns, since it would be the same
- as running [].zero?, which results in a NoMethodError exception.
-
- Fixing this by ensuring that array columns in PostgreSQL never return
- true for number?/text? checks.
-
- *Carlos Antonio da Silva*
-
-* When connecting to a non-existant database, the error:
- `ActiveRecord::NoDatabaseError` will now be raised. When being used with Rails
- the error message will include information on how to create a database:
- `rake db:create`. Supported adapters: postgresql, mysql, mysql2, sqlite3
-
- *Richard Schneeman*
-
-* Do not raise `'cannot touch on a new record object'` exception on destroying
- already destroyed `belongs_to` association with `touch: true` option.
-
- Fixes #13445.
+* PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures
+ different spellings of timestamps are treated the same.
Example:
- # Given Comment has belongs_to :post, touch: true
- comment.post.destroy
- comment.destroy # no longer raises an error
+ mytimestamp.simplified_type('timestamp without time zone')
+ # => :datetime
+ mytimestamp.simplified_type('timestamp(6) without time zone')
+ # => also :datetime (previously would be :timestamp)
- *Paul Nikitochkin*
+ See #14513.
-* Fix a bug when assigning an array containing string numbers to a
- PostgreSQL integer array column.
+ *Jefferson Lai*
- Fixes #13444.
+* `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions.
- Example:
-
- # Given Book#ratings is of type :integer, array: true
- Book.new(ratings: [1, 2]) # worked before
- Book.new(ratings: ['1', '2']) # now works as well
-
- *Damien Mathieu*
-
-* Improve the default select when `from` is used.
+ Fixes #14841.
- Previously, if you did something like Topic.from(:temp_topics), it
- would generate SQL like:
+ *Lucas Mazza*
- SELECT topics.* FROM temp_topics;
+* Fix name collision with `Array#select!` with `Relation#select!`.
- Which is will cause an error since there's not a topics table to select
- from.
+ Fixes #14752.
- Now the default if you use from is just `*`:
+ *Earl St Sauver*
- SELECT * FROM temp_topics;
+* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`.
- *Cody Cutrer*
+ 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.
-* Fix `PostgreSQL` insert to properly extract table name from multiline string SQL.
+ Fixes #14537.
- Previously, executing an insert SQL in `PostgreSQL` with a command like this:
+ *Jan Habermann*
- insert into articles(
- number)
- values(
- 5152
- )
-
- would not work because the adapter was unable to extract the correct `articles`
- table name.
+* `@destroyed` should always be set to `false` when an object is duped.
*Kuldeep Aggarwal*
-* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert
- to an `Array` by calling `#to_a` before using these methods.
-
- It intends to prevent odd bugs and confusion in code that call mutator
- methods directly on the `Relation`.
+* Fixed has_many association to make it support irregular inflections.
- Example:
+ Fixes #8928.
- # Instead of this
- Author.where(name: 'Hank Moody').compact!
+ *arthurnn*, *Javier Goizueta*
- # Now you have to do this
- authors = Author.where(name: 'Hank Moody').to_a
- authors.compact!
+* Fixed a problem where count used with a grouping was not returning a Hash.
- *Lauro Caetano*
+ Fixes #14721.
-* Better support for `where()` conditions that use a `belongs_to`
- association name.
+ *Eric Chahin*
- Using the name of an association in `where` previously worked only
- if the value was a single `ActiveRecord::Base` object. e.g.
-
- Post.where(author: Author.first)
-
- Any other values, including `nil`, would cause invalid SQL to be
- generated. This change supports arguments in the `where` query
- conditions where the key is a `belongs_to` association name and the
- value is `nil`, an `Array` of `ActiveRecord::Base` objects, or an
- `ActiveRecord::Relation` object.
-
- class Post < ActiveRecord::Base
- belongs_to :author
- end
-
- `nil` value finds records where the association is not set:
-
- Post.where(author: nil)
- # SELECT "posts".* FROM "posts" WHERE "posts"."author_id" IS NULL
-
- `Array` values find records where the association foreign key
- matches the ids of the passed ActiveRecord models, resulting
- in the same query as `Post.where(author_id: [1,2])`:
-
- authors_array = [Author.find(1), Author.find(2)]
- Post.where(author: authors_array)
- # SELECT "posts".* FROM "posts" WHERE "posts"."author_id" IN (1, 2)
-
- `ActiveRecord::Relation` values find records using the same
- query as `Post.where(author_id: Author.where(last_name: "Emde"))`
-
- Post.where(author: Author.where(last_name: "Emde"))
- # SELECT "posts".* FROM "posts"
- # WHERE "posts"."author_id" IN (
- # SELECT "authors"."id" FROM "authors"
- # WHERE "authors"."last_name" = 'Emde')
-
- Polymorphic `belongs_to` associations will continue to be handled
- appropriately, with the polymorphic `association_type` field added
- to the query to match the base class of the value. This feature
- previously only worked when the value was a single `ActveRecord::Base`.
-
- class Post < ActiveRecord::Base
- belongs_to :author, polymorphic: true
- end
-
- Post.where(author: Author.where(last_name: "Emde"))
- # Generates a query similar to:
- Post.where(author_id: Author.where(last_name: "Emde"), author_type: "Author")
-
- *Martin Emde*
-
-* Respect temporary option when dropping tables with MySQL.
-
- Normal DROP TABLE also works, but commits the transaction.
-
- drop_table :temporary_table, temporary: true
-
- *Cody Cutrer*
-
-* Add option to create tables from a query.
-
- create_table(:long_query, temporary: true,
- as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
-
- Generates:
-
- CREATE TEMPORARY TABLE long_query AS
- SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
-
- *Cody Cutrer*
-
-* `db:test:clone` and `db:test:prepare` must load Rails environment.
-
- `db:test:clone` and `db:test:prepare` use `ActiveRecord::Base`. configurations,
- so we need to load the Rails environment, otherwise the config wont be in place.
-
- *arthurnn*
-
-* Use the right column to type cast grouped calculations with custom expressions.
-
- Fixes #13230.
+* `sanitize_sql_like` helper method to escape a string for safe use in a SQL
+ LIKE statement.
Example:
- # Before
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => '0.5' }
-
- # After
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => 0.5 }
-
- *Paul Nikitochkin*
-
-* Polymorphic `belongs_to` associations with the `touch: true` option set update the timestamps of
- the old and new owner correctly when moved between owners of different types.
-
- Example:
-
- class Rating < ActiveRecord::Base
- belongs_to :rateable, polymorphic: true, touch: true
+ class Article
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term))
+ end
end
- rating = Rating.create rateable: Song.find(1)
- rating.update_attributes rateable: Book.find(2) # => timestamps of Song(1) and Book(2) are updated
-
- *Severin Schoepke*
-
-* Improve formatting of migration exception messages: make them easier to read
- with line breaks before/after, and improve the error for pending migrations.
+ Article.search("20% _reduction_")
+ # => Query looks like "... title LIKE '20\% \_reduction\_' ..."
- *John Bachir*
+ *Rob Gilson*, *Yves Senn*
-* Fix `last` with `offset` to return the proper record instead of always the last one.
+* Do not quote uuid default value on `change_column`.
- Example:
-
- Model.offset(4).last
- # => returns the 4th record from the end.
+ Fixes #14604.
- Fixes #7441.
+ *Eric Chahin*
- *kostya*, *Lauro Caetano*
-
-* `type_to_sql` returns a `String` for unmapped columns. This fixes an error
- when using unmapped PostgreSQL array types.
+* The comparison between `Relation` and `CollectionProxy` should be consistent.
Example:
- change_colum :table, :column, :bigint, array: true
-
- Fixes #13146.
-
- *Jens Fahnenbruck*, *Yves Senn*
-
-* Fix `QueryCache` to work with nested blocks, so that it will only clear the existing cache
- after leaving the outer block instead of clearing it right after the inner block is finished.
-
- *Vipul A M*
-
-* The ERB in fixture files is no longer evaluated in the context of the main
- object. Helper methods used by multiple fixtures should be defined on the
- class object returned by `ActiveRecord::FixtureSet.context_class`.
+ author.posts == Post.where(author_id: author.id)
+ # => true
+ Post.where(author_id: author.id) == author.posts
+ # => true
- *Victor Costan*
-
-* Previously, the `has_one` macro incorrectly accepted the `counter_cache`
- option, but never actually supported it. Now it will raise an `ArgumentError`
- when using `has_one` with `counter_cache`.
-
- *Godfrey Chan*
-
-* Implement `rename_index` natively for MySQL >= 5.7.
-
- *Cody Cutrer*
-
-* Fix bug when validating the uniqueness of an aliased attribute.
-
- Fixes #12402.
+ Fixes #13506.
*Lauro Caetano*
-* Update counter cache on a `has_many` relationship regardless of default scope.
-
- Fixes #12952.
-
- *Uku Taht*
-
-* `rename_index` adds the new index before removing the old one. This allows to
- rename indexes on columns with a foreign key and prevents the following error:
-
- Cannot drop index 'index_engines_on_car_id': needed in a foreign key constraint
-
- *Cody Cutrer*, *Yves Senn*
-
-* Raise `ActiveRecord::RecordNotDestroyed` when a replaced child
- marked with `dependent: destroy` fails to be destroyed.
-
- Fixex #12812.
-
- *Brian Thomas Storti*
-
-* Fix validation on uniqueness of empty association.
-
- *Evgeny Li*
-
-* Make `ActiveRecord::Relation#unscope` affect relations it is merged in to.
-
- *Jon Leighton*
-
-* Use strings to represent non-string `order_values`.
-
- *Yves Senn*
-
-* Checks to see if the record contains the foreign key to set the inverse automatically.
-
- *Edo Balvers*
-
-* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from a model's attribute or method.
-
- Example:
-
- class User < ActiveRecord::Base
- to_param :name
- end
-
- user = User.find_by(name: 'Fancy Pants')
- user.id # => 123
- user.to_param # => "123-fancy-pants"
-
- *Javan Makhmali*
-
-* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on models.
-
- Example:
-
- Post.no_touching do
- Post.first.touch
- end
-
- *Sam Stephenson*, *Damien Mathieu*
-
-* Prevent the counter cache from being decremented twice when destroying
- a record on a `has_many :through` association.
-
- Fixes #11079.
-
- *Dmitry Dedov*
-
-* Unify boolean type casting for `MysqlAdapter` and `Mysql2Adapter`.
- `type_cast` will return `1` for `true` and `0` for `false`.
-
- Fixes #11119.
-
- *Adam Williams*, *Yves Senn*
-
-* Fix bug where `has_one` association record update result in crash, when replaced with itself.
-
- Fixes #12834.
-
- *Denis Redozubov*, *Sergio Cambra*
-
-* Log bind variables after they are type casted. This makes it more
- transparent what values are actually sent to the database.
+* Calling `delete_all` on an unloaded `CollectionProxy` no longer
+ generates a SQL statement containing each id of the collection:
- irb(main):002:0> Event.find("im-no-integer")
- # Before: ... WHERE "events"."id" = $1 LIMIT 1 [["id", "im-no-integer"]]
- # After: ... WHERE "events"."id" = $1 LIMIT 1 [["id", 0]]
-
- *Yves Senn*
-
-* Fix uninitialized constant `TransactionState` error when `Marshall.load` is used on an Active Record result.
-
- Fixes #12790.
-
- *Jason Ayre*
-
-* `.unscope` now removes conditions specified in `default_scope`.
-
- *Jon Leighton*
-
-* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing, named where condition.
+ Before:
- Examples:
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
+ AND `model`.`id` IN (1, 2, 3...)
- Post.where(trashed: true).where(trashed: false) #=> WHERE `trashed` = 1 AND `trashed` = 0
- Post.where(trashed: true).rewhere(trashed: false) #=> WHERE `trashed` = 0
- Post.where(active: true).where(trashed: true).rewhere(trashed: false) #=> WHERE `active` = 1 AND `trashed` = 0
+ After:
- *DHH*
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
-* Extend `ActiveRecord::Base#cache_key` to take an optional list of timestamp attributes of which the highest will be used.
+ *Eileen M. Uchitelle*, *Aaron Patterson*
- Example:
+* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select`
+ which created invalid SQL.
- # last_reviewed_at will be used, if that's more recent than updated_at, or vice versa
- Person.find(5).cache_key(:updated_at, :last_reviewed_at)
+ Fixes #13648.
- *DHH*
+ *Simon Woker*
-* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values map to integers in the database, but can be queried by name.
+* PostgreSQL adapter only warns once for every missing OID per connection.
- Example:
+ Fixes #14275.
- class Conversation < ActiveRecord::Base
- enum status: [:active, :archived]
- end
+ *Matthew Draper*, *Yves Senn*
- Conversation::STATUS # => { active: 0, archived: 1 }
+* PostgreSQL adapter automatically reloads it's type map when encountering
+ unknown OIDs.
- # conversation.update! status: 0
- conversation.active!
- conversation.active? # => true
- conversation.status # => "active"
+ Fixes #14678.
- # conversation.update! status: 1
- conversation.archived!
- conversation.archived? # => true
- conversation.status # => "archived"
+ *Matthew Draper*, *Yves Senn*
- # conversation.update! status: 1
- conversation.status = :archived
+* Fix insertion of records via `has_many :through` association with scope.
- *DHH*
+ Fixes #3548.
-* `ActiveRecord::Base#attribute_for_inspect` now truncates long arrays (more than 10 elements).
+ *Ivan Antropov*
- *Jan Bernacki*
+* Auto-generate stable fixture UUIDs on PostgreSQL.
-* Allow for the name of the `schema_migrations` table to be configured.
+ Fixes: #11524
- *Jerad Phelps*
+ *Roderick van Domburg*
-* Do not add to scope includes values from through associations.
- Fixed bug when providing `includes` in through association scope, and fetching targets.
+* Fixed a problem where an enum would overwrite values of another enum
+ with the same name in an unrelated class.
- Example:
+ Fixes #14607.
- class Vendor < ActiveRecord::Base
- has_many :relationships, -> { includes(:user) }
- has_many :users, through: :relationships
- end
+ *Evan Whalen*
- vendor = Vendor.first
+* PostgreSQL and SQLite string columns no longer have a default limit of 255.
- # Before
+ Fixes #13435, #9153.
- vendor.users.to_a # => Raises exception: not found `:user` for `User`
+ *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn*
- # After
+* Make possible to have an association called `records`.
- vendor.users.to_a # => No exception is raised
+ Fixes #11645.
- Fixes #12242, #9517, #10240.
+ *prathamesh-sonpatki*
- *Paul Nikitochkin*
+* `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.
-* Type cast json values on write, so that the value is consistent
- with reading from the database.
+ Fixes #14003.
- Example:
+ *Jefferson Lai*
- x = JsonDataType.new tags: {"string" => "foo", :symbol => :bar}
+* Block a few default Class methods as scope name.
- # Before:
- x.tags # => {"string" => "foo", :symbol => :bar}
+ For instance, this will raise:
- # After:
- x.tags # => {"string" => "foo", "symbol" => "bar"}
+ scope :public, -> { where(status: 1) }
- *Severin Schoepke*
-
-* `ActiveRecord::Store` works together with PG `hstore` columns.
-
- Fixes #12452.
-
- *Yves Senn*
-
-* Fix bug where `ActiveRecord::Store` used a global `Hash` to keep track of
- all registered `stored_attributes`. Now every subclass of
- `ActiveRecord::Base` has it's own `Hash`.
-
- *Yves Senn*
+ *arthurnn*
-* Save `has_one` association when primary key is manually set.
+* Fixed error when using `with_options` with lambda.
- Fixes #12302.
+ Fixes #9805.
*Lauro Caetano*
-* Allow any version of BCrypt when using `has_secure_password`.
-
- *Mike Perham*
-
-* Sub-query generated for `Relation` passed as array condition did not take in account
- bind values and have invalid syntax.
-
- Generate sub-query with inline bind values.
-
- Fixes #12586.
-
- *Paul Nikitochkin*
-
-* Fix a bug where rake db:structure:load crashed when the path contained
- spaces.
-
- *Kevin Mook*
+* Switch `sqlite3:///` URLs (which were temporarily
+ deprecated in 4.1) from relative to absolute.
-* `ActiveRecord::QueryMethods#unscope` unscopes negative equality
+ If you still want the previous interpretation, you should replace
+ `sqlite3:///my/path` with `sqlite3:my/path`.
- Allows you to call `#unscope` on a relation with negative equality
- operators, i.e. `Arel::Nodes::NotIn` and `Arel::Nodes::NotEqual` that have
- been generated through the use of `where.not`.
+ *Matthew Draper*
- *Eric Hankins*
-
-* Raise an exception when model without primary key calls `.find_with_ids`.
-
- *Shimpei Makimoto*
-
-* Make `Relation#empty?` use `exists?` instead of `count`.
-
- *Szymon Nowak*
-
-* `rake db:structure:dump` no longer crashes when the port was specified as `Fixnum`.
-
- *Kenta Okamoto*
-
-* `NullRelation#pluck` takes a list of columns
-
- The method signature in `NullRelation` was updated to mimic that in
- `Calculations`.
-
- *Derek Prior*
-
-* `scope_chain` should not be mutated for other reflections.
-
- Currently `scope_chain` uses same array for building different
- `scope_chain` for different associations. During processing
- these arrays are sometimes mutated and because of in-place
- mutation the changed `scope_chain` impacts other reflections.
-
- Fix is to dup the value before adding to the `scope_chain`.
-
- Fixes #3882.
-
- *Neeraj Singh*
-
-* Prevent the inversed association from being reloaded on save.
-
- Fixes #9499.
-
- *Dmitry Polushkin*
-
-* Generate subquery for `Relation` if it passed as array condition for `where`
- method.
+* Treat blank UUID values as `nil`.
Example:
- # Before
- Blog.where('id in (?)', Blog.where(id: 1))
- # => SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = 1
- # => SELECT "blogs".* FROM "blogs" WHERE (id IN (1))
-
- # After
- Blog.where('id in (?)', Blog.where(id: 1).select(:id))
- # => SELECT "blogs".* FROM "blogs"
- # WHERE "blogs"."id" IN (SELECT "blogs"."id" FROM "blogs" WHERE "blogs"."id" = 1)
-
- Fixes #12415.
+ Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil>
- *Paul Nikitochkin*
+ *Dmitry Lavrov*
-* For missed association exception message
- which is raised in `ActiveRecord::Associations::Preloader` class
- added owner record class name in order to simplify to find problem code.
+* Enable support for materialized views on PostgreSQL >= 9.3.
- *Paul Nikitochkin*
-
-* `has_and_belongs_to_many` is now transparently implemented in terms of
- `has_many :through`. Behavior should remain the same, if not, it is a bug.
-
-* `create_savepoint`, `rollback_to_savepoint` and `release_savepoint` accept
- a savepoint name.
-
- *Yves Senn*
+ *Dave Lee*
-* Make `next_migration_number` accessible for third party generators.
+* The PostgreSQL adapter supports custom domains. Fixes #14305.
*Yves Senn*
-* Objects instantiated using a null relationship will now retain the
- attributes of the where clause.
+* 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`.
- Fixes #11676, #11675, #11376.
-
- *Paul Nikitochkin*, *Peter Brown*, *Nthalk*
-
-* Fixed `ActiveRecord::Associations::CollectionAssociation#find`
- when using `has_many` association with `:inverse_of` and finding an array of one element,
- it should return an array of one element too.
-
- *arthurnn*
-
-* Callbacks on has_many should access the in memory parent if a inverse_of is set.
-
- *arthurnn*
-
-* `ActiveRecord::ConnectionAdapters.string_to_time` respects
- string with timezone (e.g. Wed, 04 Sep 2013 20:30:00 JST).
-
- Fixes #12278.
-
- *kennyj*
-
-* Calling `update_attributes` will now throw an `ArgumentError` whenever it
- gets a `nil` argument. More specifically, it will throw an error if the
- argument that it gets passed does not respond to to `stringify_keys`.
-
- Example:
-
- @my_comment.update_attributes(nil) # => raises ArgumentError
-
- *John Wang*
-
-* Deprecate `quoted_locking_column` method, which isn't used anywhere.
-
- *kennyj*
-
-* Migration dump UUID default functions to schema.rb.
-
- Fixes #10751.
-
- *kennyj*
-
-* Fixed a bug in `ActiveRecord::Associations::CollectionAssociation#find_by_scan`
- when using `has_many` association with `:inverse_of` option and UUID primary key.
-
- Fixes #10450.
-
- *kennyj*
-
-* Fix: joins association, with defined in the scope block constraints by using several
- where constraints and at least of them is not `Arel::Nodes::Equality`,
- generates invalid SQL expression.
-
- Fixes #11963.
-
- *Paul Nikitochkin*
-
-* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed
- query to fetch results rather than loading the entire collection.
-
- *Lann Martin*
-
-* Make possible to run SQLite rake tasks without the `Rails` constant defined.
-
- *Damien Mathieu*
-
-* Allow Relation#from to accept other relations with bind values.
-
- *Ryan Wallace*
-
-* Fix inserts with prepared statements disabled.
-
- Fixes #12023.
-
- *Rafael Mendonça França*
-
-* Setting a has_one association on a new record no longer causes an empty
- transaction.
-
- *Dylan Thacker-Smith*
-
-* Fix `AR::Relation#merge` sometimes failing to preserve `readonly(false)` flag.
-
- *thedarkone*
-
-* Re-use `order` argument pre-processing for `reorder`.
-
- *Paul Nikitochkin*
-
-* Fix PredicateBuilder so polymorphic association keys in `where` clause can
- accept objects other than direct descendants of `ActiveRecord::Base` (decorated
- models, for example).
-
- *Mikhail Dieterle*
-
-* PostgreSQL adapter recognizes negative money values formatted with
- parentheses (eg. `($1.25) # => -1.25`)).
- Fixes #11899.
+ See #7814.
*Yves Senn*
-* Stop interpreting SQL 'string' columns as :string type because there is no
- common STRING datatype in SQL.
-
- *Ben Woosley*
-
-* `ActiveRecord::FinderMethods#exists?` returns `true`/`false` in all cases.
+* Fixed error when specifying a non-empty default value on a PostgreSQL array column.
- *Xavier Noria*
+ Fixes #10613.
-* Assign inet/cidr attribute with `nil` value for invalid address.
+ *Luke Steensen*
- Example:
-
- record = User.new
- record.logged_in_from_ip # is type of an inet or a cidr
-
- # Before:
- record.logged_in_from_ip = 'bad ip address' # raise exception
-
- # After:
- record.logged_in_from_ip = 'bad ip address' # do not raise exception
- record.logged_in_from_ip # => nil
- record.logged_in_from_ip_before_type_cast # => 'bad ip address'
+* Make possible to change `record_timestamps` inside Callbacks.
- *Paul Nikitochkin*
+ *Tieg Zaharia*
-* `add_to_target` now accepts a second optional `skip_callbacks` argument
+* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
+ custom primary key that didn't save due to validation error.
- If truthy, it will skip the :before_add and :after_add callbacks.
+ Fixes #14393.
- *Ben Woosley*
+ *Chris Finne*
-* Fix interactions between `:before_add` callbacks and nested attributes
- assignment of `has_many` associations, when the association was not
- yet loaded:
+* Introduce `validate` as an alias for `valid?`.
- - A `:before_add` callback was being called when a nested attributes
- assignment assigned to an existing record.
+ This is more intuitive when you want to run validations but don't care about the return value.
- - Nested Attributes assignment did not affect the record in the
- association target when a `:before_add` callback triggered the
- loading of the association
+ *Henrik Nyh*
- *Jörg Schray*
+* Create indexes inline in CREATE TABLE for MySQL.
-* Allow enable_extension migration method to be revertible.
+ This is important, because adding an index on a temporary table after it has been created
+ would commit the transaction.
- *Eric Tipton*
-
-* Type cast hstore values on write, so that the value is consistent
- with reading from the database.
+ It also allows creating and dropping indexed tables with fewer queries and fewer permissions
+ required.
Example:
- x = Hstore.new tags: {"bool" => true, "number" => 5}
-
- # Before:
- x.tags # => {"bool" => true, "number" => 5}
-
- # After:
- x.tags # => {"bool" => "true", "number" => "5"}
-
- *Yves Senn* , *Severin Schoepke*
-
-* Fix multidimensional PG arrays containing non-string items.
-
- *Yves Senn*
-
-* Fixes bug when using includes combined with select, the select statement was overwritten.
-
- Fixes #11773.
-
- *Edo Balvers*
-
-* Load fixtures from linked folders.
-
- *Kassio Borges*
-
-* Create a directory for sqlite3 file if not present on the system.
-
- *Richard Schneeman*
-
-* Removed redundant override of `xml` column definition for PG,
- in order to use `xml` column type instead of `text`.
-
- *Paul Nikitochkin*, *Michael Nikitochkin*
-
-* Revert `ActiveRecord::Relation#order` change that make new order
- prepend the old one.
-
- Before:
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY created_at desc, name asc
-
- After:
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY name asc, created_at desc
-
- This also affects order defined in `default_scope` or any kind of associations.
-
-* Add ability to define how a class is converted to Arel predicates.
- For example, adding a very vendor specific regex implementation:
-
- regex_handler = proc do |column, value|
- Arel::Nodes::InfixOperation.new('~', column, value.source)
+ create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t|
+ t.index :zip
end
- ActiveRecord::PredicateBuilder.register_handler(Regexp, regex_handler)
-
- *Sean Griffin & @joannecheng*
-
-* Don't allow `quote_value` to be called without a column.
-
- Some adapters require column information to do their job properly.
- By enforcing the provision of the column for this internal method
- we ensure that those using adapters that require column information
- will always get the proper behavior.
-
- *Ben Woosley*
-
-* When using optimistic locking, `update` was not passing the column to `quote_value`
- to allow the connection adapter to properly determine how to quote the value. This was
- affecting certain databases that use specific column types.
-
- Fixes #6763.
-
- *Alfred Wong*
-
-* rescue from all exceptions in `ConnectionManagement#call`
-
- Fixes #11497.
-
- As `ActiveRecord::ConnectionAdapters::ConnectionManagement` middleware does
- not rescue from Exception (but only from StandardError), the Connection
- Pool quickly runs out of connections when multiple erroneous Requests come
- in right after each other.
-
- Rescuing from all exceptions and not just StandardError, fixes this
- behaviour.
-
- *Vipul A M*
-
-* `change_column` for PostgreSQL adapter respects the `:array` option.
-
- *Yves Senn*
-
-* Remove deprecation warning from `attribute_missing` for attributes that are columns.
-
- *Arun Agrawal*
-
-* Remove extra decrement of transaction deep level.
-
- Fixes #4566.
-
- *Paul Nikitochkin*
-
-* Reset @column_defaults when assigning `locking_column`.
- We had a potential problem. For example:
-
- class Post < ActiveRecord::Base
- self.column_defaults # if we call this unintentionally before setting locking_column ...
- self.locking_column = 'my_locking_column'
- end
-
- Post.column_defaults["my_locking_column"]
- => nil # expected value is 0 !
+ # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query
- *kennyj*
+ *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
-* Remove extra select and update queries on save/touch/destroy ActiveRecord model
- with belongs to reflection with option `touch: true`.
+* Save `has_one` association even if the record doesn't changed.
- Fixes #11288.
+ Fixes #14407.
- *Paul Nikitochkin*
-
-* Remove deprecated nil-passing to the following `SchemaCache` methods:
- `primary_keys`, `tables`, `columns` and `columns_hash`.
-
- *Yves Senn*
-
-* Remove deprecated block filter from `ActiveRecord::Migrator#migrate`.
-
- *Yves Senn*
-
-* Remove deprecated String constructor from `ActiveRecord::Migrator`.
-
- *Yves Senn*
-
-* Remove deprecated `scope` use without passing a callable object.
-
- *Arun Agrawal*
-
-* Remove deprecated `transaction_joinable=` in favor of `begin_transaction`
- with `:joinable` option.
-
- *Arun Agrawal*
-
-* Remove deprecated `decrement_open_transactions`.
-
- *Arun Agrawal*
-
-* Remove deprecated `increment_open_transactions`.
-
- *Arun Agrawal*
+ *Rafael Mendonça França*
-* Remove deprecated `PostgreSQLAdapter#outside_transaction?`
- method. You can use `#transaction_open?` instead.
+* Use singular table name in generated migrations when
+ `ActiveRecord::Base.pluralize_table_names` is `false`.
- *Yves Senn*
+ Fixes #13426.
-* Remove deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
- `ActiveRecord::Fixtures.default_fixture_model_name`.
+ *Kuldeep Aggarwal*
- *Vipul A M*
+* `touch` accepts many attributes to be touched at once.
-* Removed deprecated `columns_for_remove` from `SchemaStatements`.
+ Example:
- *Neeraj Singh*
+ # touches :signed_at, :sealed_at, and :updated_at/on attributes.
+ Photo.last.touch(:signed_at, :sealed_at)
-* Remove deprecated `SchemaStatements#distinct`.
+ *James Pinto*
- *Francesco Rodriguez*
+* `rake db:structure:dump` only dumps schema information if the schema
+ migration table exists.
-* Move deprecated `ActiveRecord::TestCase` into the rails test
- suite. The class is no longer public and is only used for internal
- Rails tests.
+ Fixes #14217.
*Yves Senn*
-* Removed support for deprecated option `:restrict` for `:dependent`
- in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `delete_sql` in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `insert_sql` in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `finder_sql` in associations.
-
- *Neeraj Singh*
-
-* Support array as root element in JSON fields.
-
- *Alexey Noskov & Francesco Rodriguez*
-
-* Removed support for deprecated `counter_sql` in associations.
+* 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.
- *Neeraj Singh*
+ *Matthew Draper*
-* Do not invoke callbacks when `delete_all` is called on collection.
+* `pk_and_sequence_for` now ensures that only the pg_depend entries
+ pointing to pg_class, and thus only sequence objects, are considered.
- Method `delete_all` should not be invoking callbacks and this
- feature was deprecated in Rails 4.0. This is being removed.
- `delete_all` will continue to honor the `:dependent` option. However
- if `:dependent` value is `:destroy` then the `:delete_all` deletion
- strategy for that collection will be applied.
+ *Josh Williams*
- User can also force a deletion strategy by passing parameter to
- `delete_all`. For example you can do `@post.comments.delete_all(:nullify)`.
+* `where.not` adds `references` for `includes` like normal `where` calls do.
- *Neeraj Singh*
-
-* Calling default_scope without a proc will now raise `ArgumentError`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `type_cast_code` from Column.
-
- *Neeraj Singh*
-
-* Removed deprecated options `delete_sql` and `insert_sql` from HABTM
- association.
-
- Removed deprecated options `finder_sql` and `counter_sql` from
- collection association.
-
- *Neeraj Singh*
-
-* Remove deprecated `ActiveRecord::Base#connection` method.
- Make sure to access it via the class.
+ Fixes #14406.
*Yves Senn*
-* Remove deprecation warning for `auto_explain_threshold_in_seconds`.
-
- *Yves Senn*
-
-* Remove deprecated `:distinct` option from `Relation#count`.
-
- *Yves Senn*
-
-* Removed deprecated methods `partial_updates`, `partial_updates?` and
- `partial_updates=`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `scoped`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `default_scopes?`.
-
- *Neeraj Singh*
-
-* Remove implicit join references that were deprecated in 4.0.
+* Extend fixture `$LABEL` replacement to allow string interpolation.
Example:
- # before with implicit joins
- Comment.where('posts.author_id' => 7)
-
- # after
- Comment.references(:posts).where('posts.author_id' => 7)
-
- *Yves Senn*
-
-* Apply default scope when joining associations. For example:
-
- class Post < ActiveRecord::Base
- default_scope -> { where published: true }
- end
-
- class Comment
- belongs_to :post
- end
-
- When calling `Comment.joins(:post)`, we expect to receive only
- comments on published posts, since that is the default scope for
- posts.
-
- Before this change, the default scope from `Post` was not applied,
- so we'd get comments on unpublished posts.
-
- *Jon Leighton*
-
-* Remove `activerecord-deprecated_finders` as a dependency.
+ martin:
+ email: $LABEL@email.com
- *Łukasz Strzałkowski*
+ users(:martin).email # => martin@email.com
-* Remove Oracle / Sqlserver / Firebird database tasks that were deprecated in 4.0.
+ *Eric Steele*
- *kennyj*
+* Add support for `Relation` be passed as parameter on `QueryCache#select_all`.
-* `find_each` now returns an `Enumerator` when called without a block, so that it
- can be chained with other `Enumerable` methods.
+ Fixes #14361.
- *Ben Woosley*
-
-* `ActiveRecord::Result.each` now returns an `Enumerator` when called without
- a block, so that it can be chained with other `Enumerable` methods.
-
- *Ben Woosley*
-
-* Flatten merged join_values before building the joins.
-
- While joining_values special treatment is given to string values.
- By flattening the array it ensures that string values are detected
- as strings and not arrays.
+ *arthurnn*
- Fixes #10669.
+* Passing an Active Record object to `find` is now deprecated. Call `.id`
+ on the object first.
- *Neeraj Singh and iwiznia*
+* Passing an Active Record object to `find` or `exists?` is now deprecated.
+ Call `.id` on the object first.
-* Do not load all child records for inverse case.
+* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
- currently `post.comments.find(Comment.first.id)` would load all
- comments for the given post to set the inverse association.
+ *Ryuta Kamizono*
- This has a huge performance penalty. Because if post has 100k
- records and all these 100k records would be loaded in memory
- even though the comment id was supplied.
+* Support for MySQL 5.6 fractional seconds.
- Fix is to use in-memory records only if loaded? is true. Otherwise
- load the records using full sql.
+ *arthurnn*, *Tatsuhiko Miyagawa*
- Fixes #10509.
+* Support for Postgres `citext` data type enabling case-insensitive where
+ values without needing to wrap in UPPER/LOWER sql functions.
- *Neeraj Singh*
+ *Troy Kruthoff*, *Lachlan Sylvester*
-* `inspect` on Active Record model classes does not initiate a
- new connection. This means that calling `inspect`, when the
- database is missing, will no longer raise an exception.
- Fixes #10936.
+* Allow strings to specify the `#order` value.
Example:
- Author.inspect # => "Author(no database connection)"
-
- *Yves Senn*
-
-* Handle single quotes in PostgreSQL default column values.
- Fixes #10881.
+ Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql
- *Dylan Markow*
+ *Marcelo Casiraghi*, *Robin Dupret*
-* Log the sql that is actually sent to the database.
+* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID"
+ warnings on enum columns.
- If I have a query that produces sql
- `WHERE "users"."name" = 'a b'` then in the log all the
- whitespace is being squeezed. So the sql that is printed in the
- log is `WHERE "users"."name" = 'a b'`.
+ *Dieter Komendera*
- Do not squeeze whitespace out of sql queries. Fixes #10982.
+* `includes` is able to detect the right preloading strategy when string
+ joins are involved.
- *Neeraj Singh*
+ Fixes #14109.
-* Fixture setup no longer depends on `ActiveRecord::Base.configurations`.
- This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`.
+ *Aaron Patterson*, *Yves Senn*
- *Yves Senn*
-
-* Fix mysql2 adapter raises the correct exception when executing a query on a
- closed connection.
-
- *Yves Senn*
+* Fixed error with validation with enum fields for records where the
+ value for any enum attribute is always evaluated as 0 during
+ uniqueness validation.
-* Ambiguous reflections are on :through relationships are no longer supported.
- For example, you need to change this:
+ Fixes #14172.
- class Author < ActiveRecord::Base
- has_many :posts
- has_many :taggings, through: :posts
- end
+ *Vilius Luneckas* *Ahmed AbouElhamayed*
- class Post < ActiveRecord::Base
- has_one :tagging
- has_many :taggings
- end
+* `before_add` callbacks are fired before the record is saved on
+ `has_and_belongs_to_many` assocations *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.
- class Tagging < ActiveRecord::Base
- end
+ Fixes #14144.
- To this:
+* Fixed STI classes not defining an attribute method if there is a
+ conflicting private method defined on its ancestors.
- class Author < ActiveRecord::Base
- has_many :posts
- has_many :taggings, through: :posts, source: :tagging
- end
+ Fixes #11569.
- class Post < ActiveRecord::Base
- has_one :tagging
- has_many :taggings
- end
-
- class Tagging < ActiveRecord::Base
- end
-
- *Aaron Patterson*
+ *Godfrey Chan*
-* Remove column restrictions for `count`, let the database raise if the SQL is
- invalid. The previous behavior was untested and surprising for the user.
- Fixes #5554.
+* Coerce strings when reading attributes. Fixes #10485.
Example:
- User.select("name, username").count
- # Before => SELECT count(*) FROM users
- # After => ActiveRecord::StatementInvalid
-
- # you can still use `count(:all)` to perform a query unrelated to the
- # selected columns
- User.select("name, username").count(:all) # => SELECT count(*) FROM users
+ book = Book.new(title: 12345)
+ book.save!
+ book.title # => "12345"
*Yves Senn*
-* Rails now automatically detects inverse associations. If you do not set the
- `:inverse_of` option on the association, then Active Record will guess the
- inverse association based on heuristics.
-
- Note that automatic inverse detection only works on `has_many`, `has_one`,
- and `belongs_to` associations. Extra options on the associations will
- also prevent the association's inverse from being found automatically.
-
- The automatic guessing of the inverse association uses a heuristic based
- on the name of the class, so it may not work for all associations,
- especially the ones with non-standard names.
+* 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.
- You can turn off the automatic detection of inverse associations by setting
- the `:inverse_of` option to `false` like so:
-
- class Taggable < ActiveRecord::Base
- belongs_to :tag, inverse_of: false
- end
-
- *John Wang*
-
-* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432.
-
- *Adam Anderson*
-
-* Usage of `implicit_readonly` is being removed`. Please use `readonly` method
- explicitly to mark records as `readonly.
- Fixes #10615.
-
- Example:
-
- user = User.joins(:todos).select("users.*, todos.title as todos_title").readonly(true).first
- user.todos_title = 'clean pet'
- user.save! # will raise error
+ 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*
-* Fix the `:primary_key` option for `has_many` associations.
-
- Fixes #10693.
+* Support for user created range types in PostgreSQL.
*Yves Senn*
-* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1.
-
- Fixes #10620.
-
- *Aaron Patterson*
-
-* Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1.
-
- *kennyj*
-
-* Deprecate `ConnectionAdapters::SchemaStatements#distinct`,
- as it is no longer used by internals.
-
- *Ben Woosley*
-
-* Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix`
- is not blank.
-
- Call `assume_migrated_upto_version` on connection to prevent it from first
- being picked up in `method_missing`.
-
- In the base class, `Migration`, `method_missing` expects the argument to be a
- table name, and calls `proper_table_name` on the arguments before sending to
- `connection`. If `table_name_prefix` or `table_name_suffix` is used, the schema
- version changes to `prefix_version_suffix`, breaking `rake test:prepare`.
-
- Fixes #10411.
-
- *Kyle Stevens*
-
-* Method `read_attribute_before_type_cast` should accept input as symbol.
-
- *Neeraj Singh*
-
-* Confirm a record has not already been destroyed before decrementing counter cache.
-
- *Ben Tucker*
-
-* Fixed a bug in `ActiveRecord#sanitize_sql_hash_for_conditions` in which
- `self.class` is an argument to `PredicateBuilder#build_from_hash`
- causing `PredicateBuilder` to call non-existent method
- `Class#reflect_on_association`.
-
- *Zach Ohlgren*
-
-* While removing index if column option is missing then raise IrreversibleMigration exception.
-
- Following code should raise `IrreversibleMigration`. But the code was
- failing since options is an array and not a hash.
-
- def change
- change_table :users do |t|
- t.remove_index [:name, :email]
- end
- end
-
- Fix was to check if the options is a Hash before operating on it.
-
- Fixes #10419.
-
- *Neeraj Singh*
-
-* Do not overwrite manually built records during one-to-one nested attribute assignment
-
- For one-to-one nested associations, if you build the new (in-memory)
- child object yourself before assignment, then the NestedAttributes
- module will not overwrite it, e.g.:
-
- class Member < ActiveRecord::Base
- has_one :avatar
- accepts_nested_attributes_for :avatar
-
- def avatar
- super || build_avatar(width: 200)
- end
- end
-
- member = Member.new
- member.avatar_attributes = {icon: 'sad'}
- member.avatar.width # => 200
-
- *Olek Janiszewski*
-
-* fixes bug introduced by #3329. Now, when autosaving associations,
- deletions happen before inserts and saves. This prevents a 'duplicate
- unique value' database error that would occur if a record being created had
- the same value on a unique indexed field as that of a record being destroyed.
-
- *Johnny Holton*
-
-* Handle aliased attributes in ActiveRecord::Relation.
-
- When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database:
-
- With the model
-
- class Topic
- alias_attribute :heading, :title
- end
-
- The call
-
- Topic.where(heading: 'The First Topic')
-
- should yield the same result as
-
- Topic.where(title: 'The First Topic')
-
- This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`.
-
- This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`.
-
- *Godfrey Chan*
-
-* Mute `psql` output when running rake db:schema:load.
-
- *Godfrey Chan*
-
-* Trigger a save on `has_one association=(associate)` when the associate contents have changed.
-
- Fixes #8856.
-
- *Chris Thompson*
-
-* Abort a rake task when missing db/structure.sql like `db:schema:load` task.
-
- *kennyj*
-
-* rake:db:test:prepare falls back to original environment after execution.
-
- *Slava Markevich*
-
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 20516bba0c..5a84792f45 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -9,6 +9,10 @@ module ActiveRecord
@association
end
+ def ==(other)
+ other == to_a
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 714f623af3..265bad7bc2 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -4,6 +4,12 @@ require 'active_support/core_ext/module/remove_method'
require 'active_record/errors'
module ActiveRecord
+ class AssociationNotFoundError < ConfigurationError #:nodoc:
+ def initialize(record, association_name)
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
+ end
+ end
+
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(reflection, associated_class = nil)
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
@@ -130,7 +136,6 @@ module ActiveRecord
autoload :JoinDependency, 'active_record/associations/join_dependency'
autoload :AssociationScope, 'active_record/associations/association_scope'
autoload :AliasTracker, 'active_record/associations/alias_tracker'
- autoload :JoinHelper, 'active_record/associations/join_helper'
end
# Clears out the association cache.
@@ -146,7 +151,7 @@ module ActiveRecord
association = association_instance_get(name)
if association.nil?
- reflection = self.class.reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -531,8 +536,8 @@ module ActiveRecord
# end
#
# @firm = Firm.first
- # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
- # @firm.invoices # selects all invoices by going through the Client join model
+ # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
+ # @firm.invoices # selects all invoices by going through the Client join model
#
# Similarly you can go through a +has_one+ association on the join model:
#
@@ -669,11 +674,14 @@ module ActiveRecord
# and member posts that use the posts table for STI. In this case, there must be a +type+
# column in the posts table.
#
+ # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
+ # The +class_name+ of the +attachable+ is passed as a String.
+ #
# class Asset < ActiveRecord::Base
# belongs_to :attachable, polymorphic: true
#
- # def attachable_type=(sType)
- # super(sType.to_s.classify.constantize.base_class.to_s)
+ # def attachable_type=(class_name)
+ # super(class_name.constantize.base_class.to_s)
# end
# end
#
@@ -765,6 +773,12 @@ module ActiveRecord
# like this can have unintended consequences.
# In the above example posts with no approved comments are not returned at all, because
# the conditions apply to the SQL statement as a whole and not just to the association.
+ #
+ # If you want to load all posts (including posts with no approved comments) then write
+ # your own LEFT OUTER JOIN query using ON
+ #
+ # Post.joins('LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = true')
+ #
# You must disambiguate column references for this fallback to happen, for example
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
#
@@ -1034,6 +1048,9 @@ module ActiveRecord
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
#
+ # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
+ #
# [collection(force_reload = false)]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
@@ -1092,9 +1109,6 @@ module ActiveRecord
# Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
- # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
- #
# === Example
#
# A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
@@ -1209,11 +1223,15 @@ module ActiveRecord
#
# The following methods for retrieval and query of a single associated object will be added:
#
+ # +association+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
+ #
# [association(force_reload = false)]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
- # and saves the associate object.
+ # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
+ # associated object when assigning a new one, even if the new one isn't saved to database.
# [build_association(attributes = {})]
# Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not
@@ -1226,9 +1244,6 @@ module ActiveRecord
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (+association+ is replaced with the symbol passed as the first argument, so
- # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
- #
# === Example
#
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
@@ -1314,6 +1329,9 @@ module ActiveRecord
# Methods will be added for retrieval and query for a single associated object, for which
# this object holds an id:
#
+ # +association+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
+ #
# [association(force_reload = false)]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
@@ -1329,9 +1347,6 @@ module ActiveRecord
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (+association+ is replaced with the symbol passed as the first argument, so
- # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
- #
# === Example
#
# A Post class declares <tt>belongs_to :author</tt>, which will add:
@@ -1451,6 +1466,9 @@ module ActiveRecord
#
# Adds the following methods for retrieval and query:
#
+ # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
+ #
# [collection(force_reload = false)]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
@@ -1492,9 +1510,6 @@ module ActiveRecord
# with +attributes+, linked to this object through the join table, and that has already been
# saved (if it passed the validation).
#
- # (+collection+ is replaced with the symbol passed as the first argument, so
- # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
- #
# === Example
#
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
@@ -1581,7 +1596,7 @@ module ActiveRecord
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
- [:before_add, :after_add, :before_remove, :after_remove].each do |k|
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table].each do |k|
hm_options[k] = options[k] if options.key? k
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0c23029981..85109aee6c 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,16 +5,48 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :table_joins, :connection
+ attr_reader :aliases, :connection
+
+ def self.empty(connection)
+ new connection, Hash.new(0)
+ end
+
+ def self.create(connection, table_joins)
+ if table_joins.empty?
+ empty connection
+ else
+ aliases = Hash.new { |h,k|
+ h[k] = initial_count_for(connection, k, table_joins)
+ }
+ new connection, aliases
+ end
+ end
+
+ def self.initial_count_for(connection, name, table_joins)
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = connection.quote_table_name(name).downcase
+
+ counts = table_joins.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ else
+ join.left.table_name == name ? 1 : 0
+ end
+ end
+
+ counts.sum
+ end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection = Base.connection, table_joins = [])
- @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
- @table_joins = table_joins
- @connection = connection
+ def initialize(connection, aliases)
+ @aliases = aliases
+ @connection = connection
end
- def aliased_table_for(table_name, aliased_name = nil)
+ def aliased_table_for(table_name, aliased_name)
table_alias = aliased_name_for(table_name, aliased_name)
if table_alias == table_name
@@ -24,9 +56,7 @@ module ActiveRecord
end
end
- def aliased_name_for(table_name, aliased_name = nil)
- aliased_name ||= table_name
-
+ 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
@@ -48,26 +78,6 @@ module ActiveRecord
private
- def initial_count_for(name)
- return 0 if Arel::Table === table_joins
-
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = connection.quote_table_name(name).downcase
-
- counts = table_joins.map do |join|
- if join.is_a?(Arel::Nodes::StringJoin)
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- else
- join.left.table_name == name ? 1 : 0
- end
- end
-
- counts.sum
- end
-
def truncate(name)
name.slice(0, connection.table_alias_length - 2)
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 67ea489b22..9ad2d2fb12 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -94,7 +94,7 @@ module ActiveRecord
# actually gets built.
def association_scope
if klass
- @association_scope ||= AssociationScope.new(self).scope
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
end
end
@@ -232,7 +232,7 @@ module ActiveRecord
# Returns true if record contains the foreign_key
def foreign_key_for?(record)
- record.attributes.has_key? reflection.foreign_key
+ record.has_attribute?(reflection.foreign_key)
end
# This should be implemented to return the values of the relevant key(s) on the owner,
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 17f056e764..f1a3b23d5a 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,52 +1,120 @@
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
- include JoinHelper
+ def self.scope(association, connection)
+ INSTANCE.scope association, connection
+ end
+
+ class BindSubstitution
+ def initialize(block)
+ @block = block
+ end
+
+ def bind_value(scope, column, value, alias_tracker)
+ substitute = alias_tracker.connection.substitute_at(
+ column, scope.bind_values.length)
+ scope.bind_values += [[column, @block.call(value)]]
+ substitute
+ end
+ end
+
+ def self.create(&block)
+ block = block ? block : lambda { |val| val }
+ new BindSubstitution.new(block)
+ end
+
+ def initialize(bind_substitution)
+ @bind_substitution = bind_substitution
+ end
+
+ INSTANCE = create
- attr_reader :association, :alias_tracker
+ def scope(association, connection)
+ klass = association.klass
+ reflection = association.reflection
+ scope = klass.unscoped
+ owner = association.owner
+ alias_tracker = AliasTracker.empty connection
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
- delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
+ scope.extending! Array(reflection.options[:extend])
+ add_constraints(scope, owner, klass, reflection, alias_tracker)
+ end
- def initialize(association)
- @association = association
- @alias_tracker = AliasTracker.new klass.connection
+ def join_type
+ Arel::Nodes::InnerJoin
end
- def scope
- scope = klass.unscoped
- scope.extending! Array(options[:extend])
- add_constraints(scope)
+ def self.get_bind_values(owner, chain)
+ bvs = []
+ chain.each_with_index do |reflection, i|
+ if reflection.source_macro == :belongs_to
+ foreign_key = reflection.foreign_key
+ else
+ foreign_key = reflection.active_record_primary_key
+ end
+
+ if reflection == chain.last
+ bvs << owner[foreign_key]
+
+ if reflection.type
+ bvs << owner.class.base_class.name
+ end
+ else
+ if reflection.type
+ bvs << chain[i + 1].klass.base_class.name
+ end
+ end
+ end
+ bvs
end
private
- def column_for(table_name, column_name)
+ 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)
columns[column_name]
end
- def bind_value(scope, column, value)
- substitute = alias_tracker.connection.substitute_at(
- column, scope.bind_values.length)
- scope.bind_values += [[column, value]]
- substitute
+ def bind_value(scope, column, value, alias_tracker)
+ @bind_substitution.bind_value scope, column, value, alias_tracker
end
- def bind(scope, table_name, column_name, value)
- column = column_for table_name, column_name
- bind_value scope, column, value
+ def bind(scope, table_name, column_name, value, tracker)
+ column = column_for table_name, column_name, tracker
+ bind_value scope, column, value, tracker
end
- def add_constraints(scope)
- tables = construct_tables
+ def add_constraints(scope, owner, assoc_klass, refl, tracker)
+ chain = refl.chain
+ scope_chain = refl.scope_chain
+
+ tables = construct_tables(chain, assoc_klass, refl, tracker)
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
if reflection.source_macro == :belongs_to
if reflection.options[:polymorphic]
- key = reflection.association_primary_key(self.klass)
+ key = reflection.association_primary_key(assoc_klass)
else
key = reflection.association_primary_key
end
@@ -58,12 +126,12 @@ module ActiveRecord
end
if reflection == chain.last
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
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.to_s, value
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
scope = scope.where(table[reflection.type].eq(bind_val))
end
else
@@ -71,7 +139,7 @@ module ActiveRecord
if reflection.type
value = chain[i + 1].klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type.to_s, value
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
scope = scope.where(table[reflection.type].eq(bind_val))
end
@@ -79,14 +147,14 @@ module ActiveRecord
end
is_first_chain = i == 0
- klass = is_first_chain ? self.klass : reflection.klass
+ 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)
+ item = eval_scope(klass, scope_chain_item, owner)
- if scope_chain_item == self.reflection.scope
+ if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes, :bind)
end
@@ -95,6 +163,7 @@ module ActiveRecord
end
scope.where_values += item.where_values
+ scope.bind_values += item.bind_values
scope.order_values |= item.order_values
end
end
@@ -102,22 +171,22 @@ module ActiveRecord
scope
end
- def alias_suffix
- reflection.name
+ def alias_suffix(refl)
+ refl.name
end
- def table_name_for(reflection)
- if reflection == self.reflection
+ 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
- super
+ reflection.table_name
end
end
- def eval_scope(klass, scope)
+ def eval_scope(klass, scope, owner)
if scope.is_a?(Relation)
scope
else
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 8272a5584c..1edd4fa3aa 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -31,6 +31,14 @@ module ActiveRecord
@updated
end
+ def decrement_counters # :nodoc:
+ with_cache_name { |name| decrement_counter name }
+ end
+
+ def increment_counters # :nodoc:
+ with_cache_name { |name| increment_counter name }
+ end
+
private
def find_target?
@@ -51,13 +59,15 @@ module ActiveRecord
end
end
- def decrement_counters
- with_cache_name { |name| decrement_counter name }
+ def decrement_counter(counter_cache_name)
+ if foreign_key_present?
+ klass.decrement_counter(counter_cache_name, target_id)
+ end
end
- def decrement_counter counter_cache_name
+ def increment_counter(counter_cache_name)
if foreign_key_present?
- klass.decrement_counter(counter_cache_name, target_id)
+ klass.increment_counter(counter_cache_name, target_id)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 3911d1b520..f085fd1cfd 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -26,6 +26,12 @@ module ActiveRecord::Associations::Builder
attr_reader :name, :scope, :options
def self.build(model, name, scope, options, &block)
+ if model.dangerous_attribute_method?(name)
+ raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
+ "this will conflict with a method #{name} already defined by Active Record. " \
+ "Please choose a different association name."
+ end
+
builder = create_builder model, name, scope, options, &block
reflection = builder.build(model)
define_accessors model, reflection
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 5ccaa55a32..47cc1f4b34 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder
private
def self.add_counter_cache_methods(mixin)
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
mixin.class_eval do
- def belongs_to_counter_cache_after_create(reflection)
- if record = send(reflection.name)
- cache_column = reflection.counter_cache_column
- record.class.increment_counter(cache_column, record.id)
- @_after_create_counter_called = true
- end
- end
-
- def belongs_to_counter_cache_before_destroy(reflection)
- foreign_key = reflection.foreign_key.to_sym
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
- record = send reflection.name
- if record && !self.destroyed?
- cache_column = reflection.counter_cache_column
- record.class.decrement_counter(cache_column, record.id)
- end
- end
- end
-
def belongs_to_counter_cache_after_update(reflection)
foreign_key = reflection.foreign_key
cache_column = reflection.counter_cache_column
@@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder
def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column
- model.after_create lambda { |record|
- record.belongs_to_counter_cache_after_create(reflection)
- }
-
- model.before_destroy lambda { |record|
- record.belongs_to_counter_cache_before_destroy(reflection)
- }
-
model.after_update lambda { |record|
record.belongs_to_counter_cache_after_update(reflection)
}
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 7909b93622..4c8c826f76 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 52531a3520..1c84973920 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -96,11 +96,31 @@ module ActiveRecord
end
def first(*args)
- first_or_last(:first, *args)
+ first_nth_or_last(:first, *args)
+ end
+
+ def second(*args)
+ first_nth_or_last(:second, *args)
+ end
+
+ def third(*args)
+ first_nth_or_last(:third, *args)
+ end
+
+ def fourth(*args)
+ first_nth_or_last(:fourth, *args)
+ end
+
+ def fifth(*args)
+ first_nth_or_last(:fifth, *args)
+ end
+
+ def forty_two(*args)
+ first_nth_or_last(:forty_two, *args)
end
def last(*args)
- first_or_last(:last, *args)
+ first_nth_or_last(:last, *args)
end
def build(attributes = {}, &block)
@@ -114,20 +134,19 @@ module ActiveRecord
end
def create(attributes = {}, &block)
- create_record(attributes, &block)
+ _create_record(attributes, &block)
end
def create!(attributes = {}, &block)
- create_record(attributes, true, &block)
+ _create_record(attributes, true, &block)
end
# Add +records+ to this association. Returns +self+ so method calls may
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
def concat(*records)
- load_target if owner.new_record?
-
if owner.new_record?
+ load_target
concat_records(records)
else
transaction { concat_records(records) }
@@ -163,11 +182,11 @@ module ActiveRecord
#
# See delete for more info.
def delete_all(dependent = nil)
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
+ if dependent && ![:nullify, :delete_all].include?(dependent)
raise ArgumentError, "Valid values are :nullify or :delete_all"
end
- dependent = if dependent.present?
+ dependent = if dependent
dependent
elsif options[:dependent] == :destroy
:delete_all
@@ -175,7 +194,7 @@ module ActiveRecord
options[:dependent]
end
- delete(:all, dependent: dependent).tap do
+ delete_all_with_dependency(dependent).tap do
reset
loaded!
end
@@ -228,15 +247,15 @@ module ActiveRecord
_options = records.extract_options!
dependent = _options[:dependent] || options[:dependent]
- if records.first == :all
- if loaded? || dependent == :destroy
- delete_or_destroy(load_target, dependent)
- else
- delete_records(:all, dependent)
- end
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ delete_or_destroy(records, dependent)
+ end
+
+ def delete_all_with_dependency(dependent)
+ if dependent == :destroy
+ delete_or_destroy(load_target, dependent)
else
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
- delete_or_destroy(records, dependent)
+ delete_records(:all, dependent)
end
end
@@ -339,7 +358,9 @@ module ActiveRecord
if owner.new_record?
replace_records(other_array, original_target)
else
- transaction { replace_records(other_array, original_target) }
+ if other_array != original_target
+ transaction { replace_records(other_array, original_target) }
+ end
end
end
@@ -348,7 +369,7 @@ module ActiveRecord
if record.new_record?
include_in_memory?(record)
else
- loaded? ? target.include?(record) : scope.exists?(record)
+ loaded? ? target.include?(record) : scope.exists?(record.id)
end
else
false
@@ -391,9 +412,23 @@ module ActiveRecord
end
private
+ def get_records
+ return scope.to_a if reflection.scope_chain.any?(&:any?)
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do
+ StatementCache.create(conn) { |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge as.scope(self, conn)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute binds, klass, klass.connection
+ end
def find_target
- records = scope.to_a
+ records = get_records
records.each { |record| set_inverse_instance(record) }
records
end
@@ -428,13 +463,13 @@ module ActiveRecord
persisted + memory
end
- def create_record(attributes, raise = false, &block)
+ def _create_record(attributes, raise = false, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
if attributes.is_a?(Array)
- attributes.collect { |attr| create_record(attr, raise, &block) }
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
else
transaction do
add_to_target(build_record(attributes)) do |record|
@@ -493,13 +528,13 @@ module ActiveRecord
target
end
- def concat_records(records)
+ def concat_records(records, should_raise = false)
result = true
records.flatten.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
- result &&= insert_record(rec) unless owner.new_record?
+ result &&= insert_record(rec, true, should_raise) unless owner.new_record?
end
end
@@ -526,7 +561,7 @@ module ActiveRecord
# * target already loaded
# * owner is new record
# * target contains new or changed record(s)
- def fetch_first_or_last_using_find?(args)
+ def fetch_first_nth_or_last_using_find?(args)
if args.first.is_a?(Hash)
true
else
@@ -564,10 +599,10 @@ module ActiveRecord
end
# Fetches the first/last using SQL if possible, otherwise from the target array.
- def first_or_last(type, *args)
+ def first_nth_or_last(type, *args)
args.shift if args.first.is_a?(Hash) && args.first.empty?
- collection = fetch_first_or_last_using_find?(args) ? scope : load_target
+ collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
collection.send(type, *args).tap do |record|
set_inverse_instance record if record.is_a? ActiveRecord::Base
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e3fc908444..84c8cfe72b 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -84,7 +84,7 @@ module ActiveRecord
#
# Be careful because this also means you're initializing a model
# object with only the fields that you've selected. If you attempt
- # to access a field that is not in the initialized record you'll
+ # to access a field except +id+ that is not in the initialized record you'll
# receive:
#
# person.pets.select(:name).first.person_id
@@ -170,6 +170,32 @@ module ActiveRecord
@association.first(*args)
end
+ # Same as +first+ except returns only the second record.
+ def second(*args)
+ @association.second(*args)
+ end
+
+ # Same as +first+ except returns only the third record.
+ def third(*args)
+ @association.third(*args)
+ end
+
+ # Same as +first+ except returns only the fourth record.
+ def fourth(*args)
+ @association.fourth(*args)
+ end
+
+ # Same as +first+ except returns only the fifth record.
+ def fifth(*args)
+ @association.fifth(*args)
+ end
+
+ # Same as +first+ except returns only the forty second record.
+ # Also known as accessing "the reddit".
+ def forty_two(*args)
+ @association.forty_two(*args)
+ end
+
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -331,7 +357,7 @@ module ActiveRecord
# Deletes all the records from the collection. For +has_many+ associations,
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
- # option. Returns an array with the deleted records.
+ # option.
#
# If no <tt>:dependent</tt> option is given, then it will follow the
# default strategy. The default strategy is <tt>:nullify</tt>. This
@@ -409,11 +435,6 @@ module ActiveRecord
# # ]
#
# person.pets.delete_all
- # # => [
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
- # # ]
#
# Pet.find(1, 2, 3)
# # => ActiveRecord::RecordNotFound
@@ -834,6 +855,10 @@ module ActiveRecord
!!@association.include?(record)
end
+ def arel
+ scope.arel
+ end
+
def proxy_association
@association
end
@@ -978,6 +1003,28 @@ module ActiveRecord
proxy_association.reload
self
end
+
+ # Unloads the association. Returns +self+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets # fetches pets from the database
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
+ #
+ # person.pets # uses the pets cache
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
+ #
+ # person.pets.reset # clears the pets cache
+ #
+ # person.pets # fetches pets from the database
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
+ def reset
+ proxy_association.reset
+ proxy_association.reset_scope
+ self
+ 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 72e0891702..aac85a36c8 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name
else
scope.count
end
@@ -71,15 +71,15 @@ module ActiveRecord
[association_scope.limit_value, count].compact.min
end
- def has_cached_counter?(reflection = reflection)
+ def has_cached_counter?(reflection = reflection())
owner.attribute_present?(cached_counter_attribute_name(reflection))
end
- def cached_counter_attribute_name(reflection = reflection)
+ def cached_counter_attribute_name(reflection = reflection())
options[:counter_cache] || "#{reflection.name}_count"
end
- def update_counter(difference, reflection = reflection)
+ def update_counter(difference, reflection = reflection())
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
owner.class.update_counters(owner.id, counter => difference)
@@ -98,7 +98,7 @@ module ActiveRecord
# it will be decremented twice.
#
# Hence this method.
- def inverse_updates_counter_cache?(reflection = reflection)
+ def inverse_updates_counter_cache?(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
inverse_reflection.counter_cache_column == counter_name
@@ -111,7 +111,7 @@ module ActiveRecord
records.each(&:destroy!)
update_counter(-records.length) unless inverse_updates_counter_cache?
else
- if records == :all
+ if records == :all || !reflection.klass.primary_key
scope = self.scope
else
scope = self.scope.where(reflection.klass.primary_key => records)
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 31b8d27892..f3af8605cd 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -18,7 +18,7 @@ module ActiveRecord
# SELECT query if you use #length.
def size
if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name(reflection)
elsif loaded?
target.size
else
@@ -30,7 +30,6 @@ module ActiveRecord
unless owner.new_record?
records.flatten.each do |record|
raise_on_type_mismatch!(record)
- record.save! if record.new_record?
end
end
@@ -40,7 +39,7 @@ module ActiveRecord
def concat_records(records)
ensure_not_nested
- records = super
+ records = super(records, true)
if owner.new_record? && records
records.flatten.each do |record|
@@ -84,12 +83,16 @@ module ActiveRecord
@through_records[record.object_id] ||= begin
ensure_mutable
- through_record = through_association.build
+ through_record = through_association.build through_scope_attributes
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def through_scope_attributes
+ scope.where_values_hash(through_association.reflection.name.to_s)
+ end
+
def save_through_record(record)
build_through_record(record).save!
ensure
@@ -199,7 +202,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- scope.to_a
+ get_records
end
# NOTE - not sure that we can actually cope with inverses here
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 295dccf34e..b7dc037a65 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -93,8 +93,8 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins)
- @alias_tracker = AliasTracker.new(base.connection, joins)
- @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
+ @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
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -163,18 +163,18 @@ module ActiveRecord
def make_outer_joins(parent, child)
tables = table_aliases_for(parent, child)
- join_type = Arel::OuterJoin
- joins = make_constraints parent, child, tables, join_type
+ join_type = Arel::Nodes::OuterJoin
+ info = make_constraints parent, child, tables, join_type
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
end
def make_inner_joins(parent, child)
tables = child.tables
- join_type = Arel::InnerJoin
- joins = make_constraints parent, child, tables, join_type
+ join_type = Arel::Nodes::InnerJoin
+ info = make_constraints parent, child, tables, join_type
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
end
def table_aliases_for(parent, node)
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 0cd2e1a816..a0e83c0a02 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -21,11 +21,15 @@ module ActiveRecord
super && reflection == other.reflection
end
+ JoinInformation = Struct.new :joins, :binds
+
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
+ bind_values = []
tables = tables.reverse
- scope_chain_iter = scope_chain.reverse_each
+ scope_chain_index = 0
+ scope_chain = scope_chain.reverse
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -44,35 +48,42 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
- scope_chain_items = scope_chain_iter.next.map do |item|
+ 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)
end
end
+ scope_chain_index += 1
- scope_chain_items.concat [klass.send(:build_default_scope)].compact
+ scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
end
- if reflection.type
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
- end
-
if rel && !rel.arel.constraints.empty?
+ bind_values.concat rel.bind_values
constraint = constraint.and rel.arel.constraints
end
+ if reflection.type
+ value = foreign_klass.base_class.name
+ column = klass.columns_hash[column.to_s]
+
+ substitute = klass.connection.substitute_at(column, bind_values.length)
+ bind_values.push [column, value]
+ constraint = constraint.and table[reflection.type].eq substitute
+ end
+
joins << table.create_join(table, table.create_on(constraint), join_type)
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
end
- joins
+ JoinInformation.new joins, bind_values
end
# Builds equality condition.
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
deleted file mode 100644
index f345d16841..0000000000
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveRecord
- module Associations
- # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
- module JoinHelper #:nodoc:
-
- def join_type
- Arel::InnerJoin
- end
-
- private
-
- def construct_tables
- chain.map do |reflection|
- alias_tracker.aliased_table_for(
- table_name_for(reflection),
- table_alias_for(reflection, reflection != self.reflection)
- )
- end
- end
-
- def table_name_for(reflection)
- reflection.table_name
- end
-
- def table_alias_for(reflection, join = false)
- name = "#{reflection.plural_name}_#{alias_suffix}"
- name << "_join" if join
- name
- end
-
- def join(table, constraint)
- table.create_join(table, table.create_on(constraint), join_type)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 83637a0409..311684d886 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -80,7 +80,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
- NULL_RELATION = Struct.new(:values).new({})
+ NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
@@ -140,36 +140,13 @@ module ActiveRecord
end
def grouped_records(association, records)
- reflection_records = records_by_reflection(association, records)
-
- reflection_records.each_with_object({}) do |(reflection, r_records),h|
- h[reflection] = r_records.group_by { |record|
- association_klass(reflection, record)
- }
- end
- end
-
- def records_by_reflection(association, records)
- records.group_by do |record|
- reflection = record.class.reflect_on_association(association)
-
- reflection || raise_config_error(record, association)
- end
- end
-
- def raise_config_error(record, association)
- raise ActiveRecord::ConfigurationError,
- "Association named '#{association}' was not found on #{record.class.name}; " \
- "perhaps you misspelled it?"
- end
-
- def association_klass(reflection, record)
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
- klass = record.read_attribute(reflection.foreign_type.to_s)
- klass && klass.constantize
- else
- reflection.klass
+ h = {}
+ records.each do |record|
+ assoc = record.association(association)
+ klasses = h[assoc.reflection] ||= {}
+ (klasses[assoc.klass] ||= []) << record
end
+ h
end
class AlreadyLoaded
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 69b65982b3..bf461070e0 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -111,12 +111,15 @@ module ActiveRecord
scope = klass.unscoped
values = reflection_scope.values
+ reflection_binds = reflection_scope.bind_values
preload_values = preload_scope.values
+ preload_binds = preload_scope.bind_values
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
+ scope.bind_values = (reflection_binds + preload_binds)
- scope.select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
if preload_values.key? :order
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 2a8530af62..1fed7f74e7 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -23,7 +23,7 @@ module ActiveRecord
reset_association owners, through_reflection.name
- middle_records = through_records.map { |(_,rec)| rec }.flatten
+ middle_records = through_records.flat_map { |(_,rec)| rec }
preloaders = preloader.preload(middle_records,
source_reflection.name,
@@ -84,7 +84,7 @@ module ActiveRecord
end
scope.references! reflection_scope.values[:references]
- scope.order! reflection_scope.values[:order] if scope.eager_loading?
+ scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
end
scope
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index e4500af5b2..f2e3a4e40f 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -18,11 +18,11 @@ module ActiveRecord
end
def create(attributes = {}, &block)
- create_record(attributes, &block)
+ _create_record(attributes, &block)
end
def create!(attributes = {}, &block)
- create_record(attributes, true, &block)
+ _create_record(attributes, true, &block)
end
def build(attributes = {})
@@ -38,8 +38,23 @@ module ActiveRecord
scope.scope_for_create.stringify_keys.except(klass.primary_key)
end
+ def get_records
+ return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?)
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do
+ StatementCache.create(conn) { |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge(as.scope(self, conn)).limit(1)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute binds, klass, klass.connection
+ end
+
def find_target
- if record = scope.first
+ if record = get_records.first
set_inverse_instance record
end
end
@@ -52,7 +67,7 @@ module ActiveRecord
replace(record)
end
- def create_record(attributes, raise_error = false)
+ def _create_record(attributes, raise_error = false)
record = build_record(attributes)
yield(record) if block_given?
saved = record.save
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index ba7d2a3782..f8a85b8a6f 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,9 +14,11 @@ module ActiveRecord
def target_scope
scope = super
chain.drop(1).each do |reflection|
+ relation = reflection.klass.all
+ relation.merge!(reflection.scope) if reflection.scope
+
scope.merge!(
- reflection.klass.all.
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
end
scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 73761520f7..4b1733619a 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -29,6 +29,8 @@ module ActiveRecord
end
}
+ BLACKLISTED_CLASS_METHODS = %w(private public protected)
+
class AttributeMethodCache
def initialize
@module = Module.new
@@ -106,20 +108,39 @@ module ActiveRecord
else
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
- defined && !ActiveRecord::Base.method_defined?(method_name) || super
+ base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
+ defined && !base_defined || super
end
end
- # A method name is 'dangerous' if it is already defined by Active Record, but
+ # A method name is 'dangerous' if it is already (re)defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
def dangerous_attribute_method?(name) # :nodoc:
method_defined_within?(name, Base)
end
- def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
if klass.method_defined?(name) || klass.private_method_defined?(name)
- if sup.method_defined?(name) || sup.private_method_defined?(name)
- klass.instance_method(name).owner != sup.instance_method(name).owner
+ if superklass.method_defined?(name) || superklass.private_method_defined?(name)
+ klass.instance_method(name).owner != superklass.instance_method(name).owner
+ else
+ true
+ end
+ else
+ false
+ end
+ end
+
+ # A class method is 'dangerous' if it is already (re)defined by Active Record, but
+ # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
+ def dangerous_class_method?(method_name)
+ 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
+ if klass.respond_to?(name, true)
+ if superklass.respond_to?(name, true)
+ klass.method(name).owner != superklass.method(name).owner
else
true
end
@@ -260,6 +281,11 @@ module ActiveRecord
}
end
+ # Placeholder so it can be overriden when needed by serialization
+ def attributes_for_coder # :nodoc:
+ attributes
+ end
+
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated upto 50
# characters, Date and Time attributes are returned in the
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 19e81abba5..99070f127b 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -38,11 +38,37 @@ module ActiveRecord
end
end
+ def initialize_dup(other) # :nodoc:
+ super
+ init_changed_attributes
+ end
+
private
+ def initialize_internals_callback
+ super
+ init_changed_attributes
+ end
+
+ def init_changed_attributes
+ @changed_attributes = nil
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
+ # optimistic locking) won't get written unless they get marked as changed
+ self.class.columns.each do |c|
+ attr, orig_value = c.name, c.default
+ changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+ end
+
# Wrap write_attribute to remember original attribute value.
def write_attribute(attr, value)
attr = attr.to_s
+ save_changed_attribute(attr, value)
+
+ super(attr, value)
+ end
+
+ def save_changed_attribute(attr, value)
# The attribute already has an unsaved change.
if attribute_changed?(attr)
old = changed_attributes[attr]
@@ -51,23 +77,20 @@ module ActiveRecord
old = clone_attribute_value(:read_attribute, attr)
changed_attributes[attr] = old if _field_changed?(attr, old, value)
end
-
- # Carry on.
- super(attr, value)
end
- def update_record(*)
+ def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
- def create_record(*)
+ def _create_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
# Serialized attributes should always be written in case they've been
# changed in place.
def keys_for_partial_write
- changed | (attributes.keys & self.class.serialized_attributes.keys)
+ changed
end
def _field_changed?(attr, old, value)
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index d484659190..c3466153d6 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -30,7 +30,8 @@ module ActiveRecord
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
- # * +class_name+ - Optional, class name that the object type should be equal to.
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
+ # or a class name that the object type should be equal to.
#
# ==== Example
#
@@ -38,13 +39,23 @@ module ActiveRecord
# class User < ActiveRecord::Base
# serialize :preferences
# end
- def serialize(attr_name, class_name = Object)
+ #
+ # # Serialize preferences using JSON as coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, JSON
+ # end
+ #
+ # # Serialize preferences as Hash using YAML coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, Hash
+ # end
+ def serialize(attr_name, class_name_or_coder = Object)
include Behavior
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
- class_name
+ coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
+ class_name_or_coder
else
- Coders::YAMLColumn.new(class_name)
+ Coders::YAMLColumn.new(class_name_or_coder)
end
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
@@ -115,6 +126,14 @@ module ActiveRecord
end
end
+ def should_record_timestamps?
+ super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
+ end
+
+ def keys_for_partial_write
+ super | (attributes.keys & self.class.serialized_attributes.keys)
+ end
+
def type_cast_attribute_for_write(column, value)
if column && coder = self.class.serialized_attributes[column.name]
Attribute.new(coder, value, :unserialized)
@@ -156,6 +175,16 @@ module ActiveRecord
super
end
end
+
+ def attributes_for_coder
+ attribute_names.each_with_object({}) do |name, attrs|
+ attrs[name] = if self.class.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
+ else
+ read_attribute(name)
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index e9622ca0c1..f149d8f127 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -35,7 +35,7 @@ module ActiveRecord
#
# === One-to-one Example
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_one :author, autosave: true
# end
#
@@ -76,7 +76,7 @@ module ActiveRecord
#
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments # :autosave option is not declared
# end
#
@@ -95,20 +95,23 @@ module ActiveRecord
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
# are new records or not:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments, autosave: true
# end
#
# post = Post.create(title: 'ruby rocks')
# post.comments.create(body: 'hello world')
# post.comments[0].body = 'hi everyone'
- # post.save # => saves both post and comment, with 'hi everyone' as body
+ # post.comments.build(body: "good morning.")
+ # post.title += "!"
+ # post.save # => saves both post and comments.
#
# Destroying one of the associated models as part of the parent's save action
# is as simple as marking it for destruction:
#
- # post.comments.last.mark_for_destruction
- # post.comments.last.marked_for_destruction? # => true
+ # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
+ # post.comments[1].mark_for_destruction
+ # post.comments[1].marked_for_destruction? # => true
# post.comments.length # => 2
#
# Note that the model is _not_ yet removed from the database:
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 1d3ec75aa1..db4d5f0129 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -294,8 +294,8 @@ module ActiveRecord #:nodoc:
extend Enum
extend Delegation::DelegateCache
+ include Core
include Persistence
- include NoTouching
include ReadonlyAttributes
include ModelSchema
include Inheritance
@@ -309,18 +309,18 @@ module ActiveRecord #:nodoc:
include Locking::Optimistic
include Locking::Pessimistic
include AttributeMethods
- include Callbacks
include Timestamp
+ include Callbacks
include Associations
include ActiveModel::SecurePassword
include AutosaveAssociation
include NestedAttributes
include Aggregations
include Transactions
+ include NoTouching
include Reflection
include Serialization
include Store
- include Core
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 35f19f0bc0..5955673b42 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -302,11 +302,11 @@ module ActiveRecord
run_callbacks(:save) { super }
end
- def create_record #:nodoc:
+ def _create_record #:nodoc:
run_callbacks(:create) { super }
end
- def update_record(*) #:nodoc:
+ def _update_record(*) #:nodoc:
run_callbacks(:update) { super }
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 cebe741daa..db80c0faee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -58,13 +58,11 @@ module ActiveRecord
# * +checkout_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
# * +reaping_frequency+: frequency in seconds to periodically run the
- # Reaper, which attempts to find and close dead connections, which can
- # occur if a programmer forgets to close a connection at the end of a
- # thread or a thread dies unexpectedly. (Default nil, which means don't
- # run the Reaper).
- # * +dead_connection_timeout+: number of seconds from last checkout
- # after which the Reaper will consider a connection reapable. (default
- # 5 seconds).
+ # Reaper, which attempts to find and recover connections from dead
+ # threads, which can occur if a programmer forgets to close a
+ # connection at the end of a thread or a thread dies unexpectedly.
+ # Regardless of this setting, the Reaper will be invoked before every
+ # blocking wait. (Default nil, which means don't schedule the Reaper).
class ConnectionPool
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
# with which it shares a Monitor. But could be a generic Queue.
@@ -222,7 +220,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -237,7 +235,6 @@ module ActiveRecord
@spec = spec
@checkout_timeout = spec.config[:checkout_timeout] || 5
- @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@reaper.run
@@ -361,11 +358,13 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
synchronize do
+ owner = conn.owner
+
conn.run_callbacks :checkin do
conn.expire
end
- release conn
+ release conn, owner
@available.add conn
end
@@ -378,22 +377,28 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- # FIXME: we might want to store the key on the connection so that removing
- # from the reserved hash will be a little easier.
- release conn
+ release conn, conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
end
- # Removes dead connections from the pool. A dead connection can occur
- # if a programmer forgets to close a connection at the end of a thread
+ # Recover lost connections for the pool. A lost connection can occur if
+ # a programmer forgets to checkin a connection at the end of a thread
# or a thread dies unexpectedly.
def reap
- synchronize do
- stale = Time.now - @dead_connection_timeout
- connections.dup.each do |conn|
- if conn.in_use? && stale > conn.last_use && !conn.active?
+ stale_connections = synchronize do
+ @connections.select do |conn|
+ conn.in_use? && !conn.owner.alive?
+ end
+ end
+
+ stale_connections.each do |conn|
+ synchronize do
+ if conn.active?
+ conn.reset!
+ checkin conn
+ else
remove conn
end
end
@@ -415,20 +420,15 @@ module ActiveRecord
elsif @connections.size < @size
checkout_new_connection
else
+ reap
@available.poll(@checkout_timeout)
end
end
- def release(conn)
- thread_id = if @reserved_connections[current_connection_id] == conn
- current_connection_id
- else
- @reserved_connections.keys.find { |k|
- @reserved_connections[k] == conn
- }
- end
+ def release(conn, owner)
+ thread_id = owner.object_id
- @reserved_connections.delete thread_id if thread_id
+ @reserved_connections.delete thread_id
end
def new_connection
@@ -538,7 +538,10 @@ module ActiveRecord
# for (not necessarily the current class).
def retrieve_connection(klass) #:nodoc:
pool = retrieve_connection_pool(klass)
- (pool && pool.connection) or raise ConnectionNotEstablished
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ conn = pool.connection
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ conn
end
# Returns true if a connection that's accessible to this class has
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 c90915c509..bc47412405 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -9,17 +9,26 @@ module ActiveRecord
# Converts an arel AST to SQL
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
- binds = binds.dup
- visitor.accept(arel.ast) do
- quote(*binds.shift.reverse)
- end
+ collected = visitor.accept(arel.ast, collector)
+ collected.compile(binds.dup, self)
else
arel
end
end
+ # This is used in the StatementCache object. It returns an object that
+ # can be used to query the database repeatedly.
+ def cacheable_query(arel) # :nodoc:
+ if prepared_statements
+ ActiveRecord::StatementCache.query visitor, arel.ast
+ else
+ ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
+ end
+ end
+
# Returns an ActiveRecord::Result instance.
def select_all(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
select(to_sql(arel, binds), name, binds)
end
@@ -39,13 +48,13 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- select_rows(to_sql(arel, []), name)
- .map { |v| v[0] }
+ arel, binds = binds_from_relation arel, []
+ select_rows(to_sql(arel, binds), name, binds).map(&:first)
end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil)
+ def select_rows(sql, name = nil, binds = [])
end
undef_method :select_rows
@@ -317,7 +326,7 @@ module ActiveRecord
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
- elsif limit.to_s =~ /,/
+ elsif limit.to_s.include?(',')
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
@@ -378,6 +387,13 @@ module ActiveRecord
row = result.rows.first
row && row.first
end
+
+ def binds_from_relation(relation, binds)
+ if relation.is_a?(Relation) && binds.empty?
+ relation, binds = relation.arel, relation.bind_values
+ end
+ [relation, binds]
+ end
end
end
end
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 adc23a6674..4a4506c7f5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -63,6 +63,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
if @query_cache_enabled && !locked?(arel)
+ arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
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 a51691bfa8..47fe501752 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def visit_AddColumn(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ 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))
end
@@ -26,7 +26,7 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ 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?
column_sql
@@ -64,7 +64,7 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options)
+ sql << " DEFAULT #{quote_value(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"
@@ -75,6 +75,12 @@ module ActiveRecord
sql
end
+ def quote_value(value, column)
+ column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
+
+ @conn.quote(value, column)
+ end
+
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index c39bf15e83..71c3a4378b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
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 88bf15bc18..aa99822389 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# index_exists?(:suppliers, :company_id, unique: true)
#
# # Check an index with a custom name exists
- # index_exists?(:suppliers, :company_id, name: "idx_company_id"
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
#
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name)
@@ -120,9 +120,9 @@ module ActiveRecord
# The name of the primary key, if one is to be added automatically.
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
#
- # Also note that this just sets the primary key in the table. You additionally
- # need to configure the primary key in the model via +self.primary_key=+.
- # Models do NOT auto-detect the primary key from their table definition.
+ # Note that Active Record models will automatically detect their
+ # primary key. This can be avoided by using +self.primary_key=+ on the model
+ # to define the key explicitly.
#
# [<tt>:options</tt>]
# Any extra options you want appended to the table definition.
@@ -186,24 +186,23 @@ module ActiveRecord
def create_table(table_name, options = {})
td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
- if !options[:as]
- unless options[:id] == false
- pk = options.fetch(:primary_key) {
- Base.get_primary_key table_name.to_s.singularize
- }
-
- td.primary_key pk, options.fetch(:id, :primary_key), options
+ if options[:id] != false && !options[:as]
+ pk = options.fetch(:primary_key) do
+ Base.get_primary_key table_name.to_s.singularize
end
- yield td if block_given?
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
+ yield td if block_given?
+
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
end
- execute schema_creation.accept td
- td.indexes.each_pair { |c,o| add_index table_name, c, o }
+ result = execute schema_creation.accept td
+ td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
+ result
end
# Creates a new join table with the name created using the lexical order of the first two
@@ -740,6 +739,40 @@ module ActiveRecord
Table.new(table_name, base)
end
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
+ column_names = Array(column_name)
+ index_name = index_name(table_name, column: column_names)
+
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
+ index_name = options[:name].to_s if options.key?(:name)
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+
+ if options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ }
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+ end
+
+ if index_name.length > max_index_length
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
+ end
+ if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
+ end
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
+
+ [index_name, index_type, index_columns, index_options, algorithm, using]
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -770,40 +803,6 @@ module ActiveRecord
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
- def add_index_options(table_name, column_name, options = {})
- column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
-
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
-
- index_type = options[:unique] ? "UNIQUE" : ""
- index_type = options[:type].to_s if options.key?(:type)
- index_name = options[:name].to_s if options.key?(:name)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
-
- if options.key?(:algorithm)
- algorithm = index_algorithms.fetch(options[:algorithm]) {
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
- }
- end
-
- using = "USING #{options[:using]}" if options[:using].present?
-
- if supports_partial_index?
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
- end
-
- if index_name.length > max_index_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
- end
- if index_name_exists?(table_name, index_name, false)
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
- end
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
-
- [index_name, index_type, index_columns, index_options, algorithm, using]
- end
-
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 2b6685499a..bc4884b538 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -23,6 +23,10 @@ module ActiveRecord
@parent = nil
end
+ def finalized?
+ @state
+ end
+
def committed?
@state == :committed
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 8aa1ce5c04..78343cf4f5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -6,6 +6,8 @@ require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation'
require 'monitor'
+require 'arel/collectors/bind'
+require 'arel/collectors/sql_string'
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -71,8 +73,8 @@ module ActiveRecord
define_callbacks :checkout, :checkin
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :last_use, :in_use, :logger
- alias :in_use? :in_use
+ attr_reader :schema_cache, :owner, :logger
+ alias :in_use? :owner
def self.type_cast_config_to_integer(config)
if config =~ SIMPLE_INT
@@ -90,13 +92,14 @@ module ActiveRecord
end
end
+ attr_reader :prepared_statements
+
def initialize(connection, logger = nil, pool = nil) #:nodoc:
super()
@connection = connection
- @in_use = false
+ @owner = nil
@instrumenter = ActiveSupport::Notifications.instrumenter
- @last_use = false
@logger = logger
@pool = pool
@schema_cache = SchemaCache.new self
@@ -104,6 +107,26 @@ module ActiveRecord
@prepared_statements = false
end
+ class BindCollector < Arel::Collectors::Bind
+ def compile(bvs, conn)
+ super(bvs.map { |bv| conn.quote(*bv.reverse) })
+ end
+ end
+
+ class SQLString < Arel::Collectors::SQLString
+ def compile(bvs, conn)
+ super(bvs)
+ end
+ end
+
+ def collector
+ if @prepared_statements
+ SQLString.new
+ else
+ BindCollector.new
+ end
+ end
+
def valid_type?(type)
true
end
@@ -114,9 +137,8 @@ module ActiveRecord
def lease
synchronize do
- unless in_use
- @in_use = true
- @last_use = Time.now
+ unless in_use?
+ @owner = Thread.current
end
end
end
@@ -127,19 +149,14 @@ module ActiveRecord
end
def expire
- @in_use = false
- end
-
- def unprepared_visitor
- self.class::BindSubstitution.new self
+ @owner = nil
end
def unprepared_statement
old_prepared_statements, @prepared_statements = @prepared_statements, false
- old_visitor, @visitor = @visitor, unprepared_visitor
yield
ensure
- @visitor, @prepared_statements = old_visitor, old_prepared_statements
+ @prepared_statements = old_prepared_statements
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -148,28 +165,19 @@ module ActiveRecord
'Abstract'
end
- # Does this adapter support migrations? Backend specific, as the
- # abstract adapter always returns +false+.
+ # Does this adapter support migrations?
def supports_migrations?
false
end
# Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables? Backend specific, as
- # the abstract adapter always returns +false+.
+ # to an Active Record class, such as join tables?
def supports_primary_key?
false
end
- # Does this adapter support using DISTINCT within COUNT? This is +true+
- # for all adapters except sqlite.
- def supports_count_distinct?
- true
- end
-
# Does this adapter support DDL rollbacks in transactions? That is, would
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
- # SQL Server, and others support this. MySQL and others do not.
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
def supports_ddl_transactions?
false
end
@@ -178,8 +186,7 @@ module ActiveRecord
false
end
- # Does this adapter support savepoints? PostgreSQL and MySQL do,
- # SQLite < 3.6.8 does not.
+ # Does this adapter support savepoints?
def supports_savepoints?
false
end
@@ -187,7 +194,6 @@ module ActiveRecord
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
- # This is false for all adapters but Firebird.
def prefetch_primary_key?(table_name = nil)
false
end
@@ -202,8 +208,7 @@ module ActiveRecord
false
end
- # Does this adapter support explain? As of this writing sqlite3,
- # mysql2, and postgresql are the only ones that do.
+ # Does this adapter support explain?
def supports_explain?
false
end
@@ -213,12 +218,17 @@ module ActiveRecord
false
end
- # Does this adapter support database extensions? As of this writing only
- # postgresql does.
+ # Does this adapter support database extensions?
def supports_extensions?
false
end
+ # Does this adapter support creating indexes in the same statement as
+ # creating the table?
+ def supports_indexes_in_create?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -227,14 +237,12 @@ module ActiveRecord
def enable_extension(name)
end
- # A list of extensions, to be filled in by adapters that support them. At
- # the moment only postgresql does.
+ # A list of extensions, to be filled in by adapters that support them.
def extensions
[]
end
# A list of index algorithms, to be filled by adapters that support them.
- # MySQL and PostgreSQL have support for them right now.
def index_algorithms
{}
end
@@ -295,7 +303,6 @@ module ActiveRecord
end
# Returns true if its required to reload the connection between requests for development mode.
- # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
false
end
@@ -330,10 +337,16 @@ module ActiveRecord
def release_savepoint(name = nil)
end
- def case_sensitive_modifier(node)
+ def case_sensitive_modifier(node, table_attribute)
node
end
+ def case_sensitive_comparison(table, attribute, column, value)
+ table_attr = table[attribute]
+ value = case_sensitive_modifier(value, table_attr) unless value.nil?
+ table_attr.eq(value)
+ end
+
def case_insensitive_comparison(table, attribute, column, value)
table[attribute].lower.eq(table.lower(value))
end
@@ -349,6 +362,14 @@ module ActiveRecord
protected
+ def translate_exception_class(e, sql)
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ @logger.error message if @logger
+ exception = translate_exception(e, message)
+ exception.set_backtrace e.backtrace
+ exception
+ end
+
def log(sql, name = "SQL", binds = [], statement_name = nil)
@instrumenter.instrument(
"sql.active_record",
@@ -358,11 +379,7 @@ module ActiveRecord
:statement_name => statement_name,
:binds => binds) { yield }
rescue => e
- message = "#{e.class.name}: #{e.message}: #{sql}"
- @logger.error message if @logger
- exception = translate_exception(e, message)
- exception.set_backtrace e.backtrace
- raise exception
+ raise translate_exception_class(e, sql)
end
def translate_exception(exception, message)
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 7768c24967..75c58ac7d9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,18 +6,31 @@ module ActiveRecord
include Savepoints
class SchemaCreation < AbstractAdapter::SchemaCreation
-
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
end
private
+
+ def visit_TableDefinition(o)
+ name = o.name
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
+
+ statements = o.columns.map { |c| accept c }
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
+
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
+ create_sql << "#{o.options}"
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
+ create_sql
+ 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)
+ add_column_options!(change_column_sql, options.merge(column: column))
add_column_position!(change_column_sql, options)
end
@@ -29,6 +42,11 @@ module ActiveRecord
end
sql
end
+
+ def index_in_create(table_name, column_name, options)
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
end
def schema_creation
@@ -165,21 +183,18 @@ module ActiveRecord
INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
+ @visitor = Arel::Visitors::MySQL.new self
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::MySQL.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
end
@@ -225,6 +240,10 @@ module ActiveRecord
version[0] >= 5
end
+ def supports_indexes_in_create?
+ true
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -298,11 +317,7 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
+ log(sql, name) { @connection.query(sql) }
end
# MysqlAdapter has to free a result after using it, so we use this method to write
@@ -463,7 +478,7 @@ module ActiveRecord
end
def bulk_change_table(table_name, operations) #:nodoc:
- sqls = operations.map do |command, args|
+ sqls = operations.flat_map do |command, args|
table, arguments = args.shift, args
method = :"#{command}_sql"
@@ -472,7 +487,7 @@ module ActiveRecord
else
raise "Unknown method called : #{method}(#{arguments.inspect})"
end
- end.flatten.join(", ")
+ end.join(", ")
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
end
@@ -592,10 +607,19 @@ module ActiveRecord
pk_and_sequence && pk_and_sequence.first
end
- def case_sensitive_modifier(node)
+ def case_sensitive_modifier(node, table_attribute)
+ node = Arel::Nodes.build_quoted node, table_attribute
Arel::Nodes::Bin.new(node)
end
+ def case_sensitive_comparison(table, attribute, column, value)
+ if column.case_sensitive?
+ table[attribute].eq(value)
+ else
+ super
+ end
+ end
+
def case_insensitive_comparison(table, attribute, column, value)
if column.case_sensitive?
super
@@ -775,7 +799,7 @@ module ActiveRecord
end.compact.join(', ')
# ...and send them all in one query
- execute("SET #{encoding} #{variable_assignments}", :skip_logging)
+ @connection.query "SET #{encoding} #{variable_assignments}"
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index f2fbd5a8f2..187eefb9e4 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -87,7 +87,7 @@ module ActiveRecord
end
end
- # Casts value (which is a String) to an appropriate instance.
+ # Casts value to an appropriate instance.
def type_cast(value)
return nil if value.nil?
return coder.load(value) if encoded?
@@ -95,7 +95,13 @@ module ActiveRecord
klass = self.class
case type
- when :string, :text then value
+ when :string, :text
+ case value
+ when TrueClass; "1"
+ when FalseClass; "0"
+ else
+ value.to_s
+ end
when :integer then klass.value_to_integer(value)
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 3f8b14bf67..b79d1a4458 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -33,9 +33,14 @@ module ActiveRecord
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = URI.parse(url)
- @adapter = @uri.scheme
+ @adapter = @uri.scheme.gsub('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
- @query = @uri.query || ''
+
+ if @uri.opaque
+ @uri.opaque, @query = @uri.opaque.split('?', 2)
+ else
+ @query = @uri.query
+ end
end
# Converts the given URL to a full connection hash.
@@ -57,38 +62,46 @@ module ActiveRecord
# Converts the query parameters of the URI into a hash.
#
- # "localhost?pool=5&reap_frequency=2"
- # # => { "pool" => "5", "reap_frequency" => "2" }
+ # "localhost?pool=5&reaping_frequency=2"
+ # # => { "pool" => "5", "reaping_frequency" => "2" }
#
# returns empty hash if no query present.
#
# "localhost"
# # => {}
def query_hash
- Hash[@query.split("&").map { |pair| pair.split("=") }]
+ Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
end
def raw_config
- query_hash.merge({
- "adapter" => @adapter,
- "username" => uri.user,
- "password" => uri.password,
- "port" => uri.port,
- "database" => database,
- "host" => uri.host })
+ if uri.opaque
+ query_hash.merge({
+ "adapter" => @adapter,
+ "database" => uri.opaque })
+ else
+ query_hash.merge({
+ "adapter" => @adapter,
+ "username" => uri.user,
+ "password" => uri.password,
+ "port" => uri.port,
+ "database" => database_from_path,
+ "host" => uri.host })
+ end
end
# Returns name of the database.
- # Sqlite3 expects this to be a full path or `:memory:`.
- def database
+ def database_from_path
if @adapter == 'sqlite3'
- if '/:memory:' == uri.path
- ':memory:'
- else
- uri.path
- end
+ # 'sqlite3:/foo' is absolute, because that makes sense. The
+ # corresponding relative version, 'sqlite3:foo', is handled
+ # elsewhere, as an "opaque".
+
+ uri.path
else
- uri.path.sub(%r{^/},"")
+ # Only SQLite uses a filename as the "database" name; for
+ # anything else, a leading slash would be silly.
+
+ uri.path.sub(%r{^/}, "")
end
end
end
@@ -124,7 +137,7 @@ module ActiveRecord
if config
resolve_connection config
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
- resolve_env_connection env.to_sym
+ resolve_symbol_connection env.to_sym
else
raise AdapterNotSpecified
end
@@ -193,42 +206,41 @@ module ActiveRecord
#
def resolve_connection(spec)
case spec
- when Symbol, String
- resolve_env_connection spec
+ when Symbol
+ resolve_symbol_connection spec
+ when String
+ resolve_string_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 "Passing a string to ActiveRecord::Base.establish_connection " \
+ "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
+ resolve_symbol_connection(spec)
+ else
+ resolve_url_connection(spec)
+ end
+ end
+
# Takes the environment such as `:production` or `:development`.
# This requires that the @configurations was initialized with a key that
# matches.
#
- #
- # Resolver.new("production" => {}).resolve_env_connection(:production)
+ # Resolver.new("production" => {}).resolve_symbol_connection(:production)
# # => {}
#
- # Takes a connection URL.
- #
- # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo")
- # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
- #
- def resolve_env_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_string_connection and
- # resolve_symbol_connection.
+ def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
- if spec.is_a?(String)
- ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
- "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
- end
resolve_connection(config)
- elsif spec.is_a?(String)
- resolve_string_connection(spec)
else
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
+ raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
end
end
@@ -237,14 +249,19 @@ module ActiveRecord
# hash and merges with the rest of the hash.
# Connection details inside of the "url" key win any merge conflicts
def resolve_hash_connection(spec)
- if url = spec.delete("url")
- connection_hash = resolve_string_connection(url)
+ if spec["url"] && spec["url"] !~ /^jdbc:/
+ connection_hash = resolve_string_connection(spec.delete("url"))
spec.merge!(connection_hash)
end
spec
end
- def resolve_string_connection(url)
+ # Takes a connection URL.
+ #
+ # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_url_connection(url)
ConnectionUrlResolver.new(url).to_hash
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 6d8e994654..233af252d6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -20,9 +20,9 @@ module ActiveRecord
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
rescue Mysql2::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
@@ -40,7 +40,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
- @visitor = BindSubstitution.new self
+ @prepared_statements = false
configure_connection
end
@@ -83,6 +83,14 @@ module ActiveRecord
@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?
@@ -213,7 +221,7 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil)
+ def select_rows(sql, name = nil, binds = [])
execute(sql, name).to_a
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7dbaa272a3..e6aa2ba921 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -36,9 +36,9 @@ module ActiveRecord
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
rescue Mysql::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
@@ -213,9 +213,9 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
- def select_rows(sql, name = nil)
+ def select_rows(sql, name = nil, binds = [])
@connection.query_with_result = true
- rows = exec_query(sql, name).rows
+ rows = exec_query(sql, name, binds).rows
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
index 20de8d1982..0b218f2bfd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
@@ -91,8 +91,9 @@ module ActiveRecord
end
def add_item_to_array(array, current_item, quoted)
- if current_item.length == 0
- elsif !quoted && current_item == 'NULL'
+ return if !quoted && current_item.length == 0
+
+ if !quoted && current_item == 'NULL'
array.push nil
else
array.push current_item
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 35ce881302..551a9289c3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -35,11 +35,11 @@ module ActiveRecord
end
end
- def hstore_to_string(object)
+ def hstore_to_string(object, array_member = false)
if Hash === object
- object.map { |k,v|
- "#{escape_hstore(k)}=>#{escape_hstore(v)}"
- }.join ','
+ string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',')
+ string = escape_hstore(string) if array_member
+ string
else
object
end
@@ -49,10 +49,10 @@ module ActiveRecord
if string.nil?
nil
elsif String === string
- Hash[string.scan(HstorePair).map { |k,v|
+ Hash[string.scan(HstorePair).map { |k, v|
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
- [k,v]
+ [k, v]
}]
else
string
@@ -142,12 +142,16 @@ module ActiveRecord
end
end
+ ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays
+
def quote_and_escape(value)
case value
when "NULL", Numeric
value
else
- "\"#{value.gsub(/"/,"\\\"")}\""
+ value = value.gsub(/\\/, ARRAY_ESCAPE)
+ value.gsub!(/"/,"\\\"")
+ "\"#{value}\""
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
new file mode 100644
index 0000000000..2cbcd5fd50
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -0,0 +1,158 @@
+module ActiveRecord
+ module ConnectionAdapters
+ # PostgreSQL-specific extensions to column definitions in a table.
+ class PostgreSQLColumn < Column #:nodoc:
+ attr_accessor :array
+
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
+ @oid_type = oid_type
+ default_value = self.class.extract_value_from_default(default)
+
+ if sql_type =~ /\[\]$/
+ @array = true
+ super(name, default_value, sql_type[0..sql_type.length - 3], null)
+ else
+ @array = false
+ super(name, default_value, sql_type, null)
+ end
+
+ @default_function = default if has_default_function?(default_value, default)
+ end
+
+ def number?
+ !array && super
+ end
+
+ def text?
+ !array && super
+ end
+
+ # :stopdoc:
+ class << self
+ include ConnectionAdapters::PostgreSQLColumn::Cast
+ include ConnectionAdapters::PostgreSQLColumn::ArrayParser
+ attr_accessor :money_precision
+ end
+ # :startdoc:
+
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
+ case default
+ when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
+ $1
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
+ $1
+ # Character types
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
+ $1.gsub(/''/, "'")
+ # Binary data types
+ when /\A'(.*)'::bytea\z/m
+ $1
+ # Date/time types
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
+ # Geometric types
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
+ # Network address types
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
+ # XML type
+ when /\A'(.*)'::xml\z/m
+ $1
+ # Arrays
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Hstore
+ when /\A'(.*)'::hstore\z/
+ $1
+ # JSON
+ when /\A'(.*)'::json\z/
+ $1
+ # Object identifier types
+ when /\A-?\d+\z/
+ $1
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
+ end
+ end
+
+ def type_cast_for_write(value)
+ if @oid_type.respond_to?(:type_cast_for_write)
+ @oid_type.type_cast_for_write(value)
+ else
+ super
+ end
+ end
+
+ def type_cast(value)
+ return if value.nil?
+ return super if encoded?
+
+ @oid_type.type_cast value
+ end
+
+ def accessor
+ @oid_type.accessor
+ end
+
+ private
+
+ def has_default_function?(default_value, default)
+ !default_value && (%r{\w+\(.*\)} === default)
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ when /^timestamp/i; nil
+ else super
+ end
+ end
+
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ if sql_type == 'money'
+ self.class.money_precision
+ elsif sql_type =~ /timestamp/i
+ $1.to_i if sql_type =~ /\((\d+)\)/
+ else
+ super
+ end
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ @oid_type.simplified_type(field_type) || super
+ end
+ 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 f349c37724..168b08ba75 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -44,10 +44,32 @@ module ActiveRecord
end
end
+ def select_value(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
+ end
+ end
+
+ def select_values(arel, name = nil)
+ arel, binds = binds_from_relation arel, []
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ if result.nfields > 0
+ result.column_values(0)
+ else
+ []
+ end
+ end
+ end
+
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
- def select_rows(sql, name = nil)
- select_raw(sql, name).last
+ def select_rows(sql, name = nil, binds = [])
+ execute_and_clear(sql, name, binds) do |result|
+ result.values
+ end
end
# Executes an INSERT query and returns the new record's ID
@@ -134,31 +156,20 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
-
- types = {}
- fields = result.fields
- fields.each_with_index do |fname, i|
- ftype = result.ftype i
- fmod = result.fmod i
- types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
- warn "unknown OID: #{fname}(#{oid}) (#{sql})"
- OID::Identity.new
- }
+ execute_and_clear(sql, name, binds) do |result|
+ types = {}
+ fields = result.fields
+ fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = get_oid_type(ftype, fmod, fname)
+ end
+ ActiveRecord::Result.new(fields, result.values, types)
end
-
- ret = ActiveRecord::Result.new(fields, result.values, types)
- result.clear
- return ret
end
def exec_delete(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
- affected = result.cmd_tuples
- result.clear
- affected
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
end
alias :exec_update :exec_delete
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index fae260a921..540b3694b5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -6,6 +6,11 @@ module ActiveRecord
module OID
class Type
def type; end
+ def simplified_type(sql_type); type end
+
+ def infinity(options = {})
+ ::Float::INFINITY * (options[:negative] ? -1 : 1)
+ end
end
class Identity < Type
@@ -14,9 +19,33 @@ module ActiveRecord
end
end
+ class String < Type
+ def type; :string end
+
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_s
+ end
+ end
+
+ class SpecializedString < OID::String
+ def type; @type end
+
+ def initialize(type)
+ @type = type
+ end
+ end
+
+ class Text < OID::String
+ def type; :text end
+ end
+
class Bit < Type
+ def type; :string end
+
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_bit value
else
value
@@ -25,6 +54,8 @@ module ActiveRecord
end
class Bytea < Type
+ def type; :binary end
+
def type_cast(value)
return if value.nil?
PGconn.unescape_bytea value
@@ -32,9 +63,11 @@ module ActiveRecord
end
class Money < Type
+ def type; :decimal end
+
def type_cast(value)
return if value.nil?
- return value unless String === value
+ return value unless ::String === value
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
@@ -76,8 +109,10 @@ module ActiveRecord
end
class Point < Type
+ def type; :string end
+
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_point value
else
value
@@ -86,13 +121,15 @@ module ActiveRecord
end
class Array < Type
+ def type; @subtype.type end
+
attr_reader :subtype
def initialize(subtype)
@subtype = subtype
end
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype
else
value
@@ -102,6 +139,8 @@ module ActiveRecord
class Range < Type
attr_reader :subtype
+ def simplified_type(sql_type); sql_type.to_sym end
+
def initialize(subtype)
@subtype = subtype
end
@@ -109,23 +148,19 @@ module ActiveRecord
def extract_bounds(value)
from, to = value[1..-2].split(',')
{
- from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from,
- to: (value[-2] == ',' || to == 'infinity') ? infinity : to,
+ from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
+ to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
exclude_start: (value[0] == '('),
exclude_end: (value[-1] == ')')
}
end
- def infinity(options = {})
- ::Float::INFINITY * (options[:negative] ? -1 : 1)
- end
-
def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end
- def to_integer(value)
- infinity?(value) ? value : value.to_i
+ def type_cast_single(value)
+ infinity?(value) ? value : @subtype.type_cast(value)
end
def type_cast(value)
@@ -133,32 +168,27 @@ module ActiveRecord
return value if value.is_a?(::Range)
extracted = extract_bounds(value)
-
- case @subtype
- when :date
- from = ConnectionAdapters::Column.value_to_date(extracted[:from])
- from -= 1.day if extracted[:exclude_start]
- to = ConnectionAdapters::Column.value_to_date(extracted[:to])
- when :decimal
- from = BigDecimal.new(extracted[:from].to_s)
- # FIXME: add exclude start for ::Range, same for timestamp ranges
- to = BigDecimal.new(extracted[:to].to_s)
- when :time
- from = ConnectionAdapters::Column.string_to_time(extracted[:from])
- to = ConnectionAdapters::Column.string_to_time(extracted[:to])
- when :integer
- from = to_integer(extracted[:from]) rescue value ? 1 : 0
- from -= 1 if extracted[:exclude_start]
- to = to_integer(extracted[:to]) rescue value ? 1 : 0
- else
- return value
+ from = type_cast_single extracted[:from]
+ to = type_cast_single extracted[:to]
+
+ if !infinity?(from) && extracted[:exclude_start]
+ if from.respond_to?(:succ)
+ from = from.succ
+ ActiveSupport::Deprecation.warn <<-MESSAGE
+Excluding the beginning of a Range is only partialy supported through `#succ`.
+This is not reliable and will be removed in the future.
+ MESSAGE
+ else
+ raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
+ end
end
-
::Range.new(from, to, extracted[:exclude_end])
end
end
class Integer < Type
+ def type; :integer end
+
def type_cast(value)
return if value.nil?
@@ -167,6 +197,8 @@ module ActiveRecord
end
class Boolean < Type
+ def type; :boolean end
+
def type_cast(value)
return if value.nil?
@@ -176,6 +208,9 @@ module ActiveRecord
class Timestamp < Type
def type; :timestamp; end
+ def simplified_type(sql_type)
+ :datetime
+ end
def type_cast(value)
return if value.nil?
@@ -187,7 +222,7 @@ module ActiveRecord
end
class Date < Type
- def type; :datetime; end
+ def type; :date; end
def type_cast(value)
return if value.nil?
@@ -199,6 +234,8 @@ module ActiveRecord
end
class Time < Type
+ def type; :time end
+
def type_cast(value)
return if value.nil?
@@ -209,6 +246,8 @@ module ActiveRecord
end
class Float < Type
+ def type; :float end
+
def type_cast(value)
return if value.nil?
@@ -217,14 +256,30 @@ module ActiveRecord
end
class Decimal < Type
+ def type; :decimal end
+
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_decimal value
end
+
+ def infinity(options = {})
+ BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ end
+ end
+
+ class Enum < Type
+ def type; :enum end
+
+ def type_cast(value)
+ value.to_s
+ end
end
class Hstore < Type
+ def type; :hstore end
+
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.hstore_to_string value
end
@@ -241,14 +296,20 @@ module ActiveRecord
end
class Cidr < Type
+ def type; :cidr end
def type_cast(value)
return if value.nil?
ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
end
end
+ class Inet < Cidr
+ def type; :inet end
+ end
class Json < Type
+ def type; :json end
+
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.json_to_string value
end
@@ -264,6 +325,13 @@ module ActiveRecord
end
end
+ class Uuid < Type
+ def type; :uuid end
+ def type_cast(value)
+ value.presence
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -310,7 +378,7 @@ module ActiveRecord
}
# Register an OID type named +name+ with a typecasting object in
- # +type+. +name+ should correspond to the `typname` column in
+ # +type+. +name+ should correspond to the `typname` column in
# the `pg_type` table.
def self.register_type(name, type)
NAMES[name] = type
@@ -327,54 +395,46 @@ module ActiveRecord
end
register_type 'int2', OID::Integer.new
- alias_type 'int4', 'int2'
- alias_type 'int8', 'int2'
- alias_type 'oid', 'int2'
-
- register_type 'daterange', OID::Range.new(:date)
- register_type 'numrange', OID::Range.new(:decimal)
- register_type 'tsrange', OID::Range.new(:time)
- register_type 'int4range', OID::Range.new(:integer)
- alias_type 'tstzrange', 'tsrange'
- alias_type 'int8range', 'int4range'
-
+ alias_type 'int4', 'int2'
+ alias_type 'int8', 'int2'
+ alias_type 'oid', 'int2'
register_type 'numeric', OID::Decimal.new
- register_type 'text', OID::Identity.new
- alias_type 'varchar', 'text'
- alias_type 'char', 'text'
- alias_type 'bpchar', 'text'
- alias_type 'xml', 'text'
-
- # FIXME: why are we keeping these types as strings?
- alias_type 'tsvector', 'text'
- alias_type 'interval', 'text'
- alias_type 'macaddr', 'text'
- alias_type 'uuid', 'text'
-
- register_type 'money', OID::Money.new
- register_type 'bytea', OID::Bytea.new
- register_type 'bool', OID::Boolean.new
- register_type 'bit', OID::Bit.new
- register_type 'varbit', OID::Bit.new
-
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
-
+ register_type 'text', OID::Text.new
+ register_type 'varchar', OID::String.new
+ alias_type 'char', 'varchar'
+ alias_type 'bpchar', 'varchar'
+ register_type 'bool', OID::Boolean.new
+ register_type 'bit', OID::Bit.new
+ alias_type 'varbit', 'bit'
register_type 'timestamp', OID::Timestamp.new
- register_type 'timestamptz', OID::Timestamp.new
+ alias_type 'timestamptz', 'timestamp'
register_type 'date', OID::Date.new
register_type 'time', OID::Time.new
- register_type 'path', OID::Identity.new
+ register_type 'money', OID::Money.new
+ register_type 'bytea', OID::Bytea.new
register_type 'point', OID::Point.new
- register_type 'polygon', OID::Identity.new
- register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
register_type 'json', OID::Json.new
- register_type 'ltree', OID::Identity.new
-
register_type 'cidr', OID::Cidr.new
- alias_type 'inet', 'cidr'
+ register_type 'inet', OID::Inet.new
+ register_type 'uuid', OID::Uuid.new
+ register_type 'xml', SpecializedString.new(:xml)
+ register_type 'tsvector', SpecializedString.new(:tsvector)
+ register_type 'macaddr', SpecializedString.new(:macaddr)
+ register_type 'citext', SpecializedString.new(:citext)
+ register_type 'ltree', SpecializedString.new(:ltree)
+
+ # FIXME: why are we keeping these types as strings?
+ alias_type 'interval', 'varchar'
+ alias_type 'path', 'varchar'
+ alias_type 'line', 'varchar'
+ alias_type 'polygon', 'varchar'
+ alias_type 'circle', 'varchar'
+ alias_type 'lseg', 'varchar'
+ alias_type 'box', 'varchar'
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 c1f978a081..403e37fde9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -4,14 +4,14 @@ module ActiveRecord
module Quoting
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- PGconn.escape_bytea(value) if value
+ @connection.escape_bytea(value) if value
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
- PGconn.unescape_bytea(value) if value
+ @connection.unescape_bytea(value) if value
end
# Quotes PostgreSQL-specific data types for SQL input.
@@ -121,7 +121,7 @@ module ActiveRecord
end
when Hash
case column.sql_type
- when 'hstore' then PostgreSQLColumn.hstore_to_string(value)
+ when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
when 'json' then PostgreSQLColumn.json_to_string(value)
else super(value, column)
end
@@ -182,6 +182,15 @@ module ActiveRecord
end
result
end
+
+ # Does not quote function default values for UUID columns
+ def quote_default_value(value, column) #:nodoc:
+ if column.type == :uuid && value =~ /\(\)/
+ value
+ else
+ quote(value)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 571257f6dd..1dc7a6f0fd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -104,14 +104,11 @@ module ActiveRecord
schema, table = Utils.extract_schema_and_table(name.to_s)
return false unless table
- binds = [[nil, table]]
- binds << [nil, schema] if schema
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind in ('v','r')
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
SQL
@@ -126,6 +123,19 @@ module ActiveRecord
SQL
end
+ def index_name_exists?(table_name, index_name, default)
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ SELECT COUNT(*)
+ FROM pg_class t
+ INNER JOIN pg_index d ON t.oid = d.indrelid
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
+ WHERE i.relkind = 'i'
+ AND i.relname = '#{index_name}'
+ AND t.relname = '#{table_name}'
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
+ SQL
+ end
+
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
result = query(<<-SQL, 'SCHEMA')
@@ -172,13 +182,15 @@ module ActiveRecord
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
- oid = type_map.fetch(oid.to_i, fmod.to_i) {
- OID::Identity.new
- }
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name)
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
+ def column_for(table_name, column_name) #:nodoc:
+ columns(table_name).detect { |c| c.name == column_name.to_s }
+ end
+
# Returns the current database name.
def current_database
query('select current_database()', 'SCHEMA')[0][0]
@@ -314,6 +326,7 @@ module ActiveRecord
AND attr.attrelid = cons.conrelid
AND attr.attnum = cons.conkey[1]
AND cons.contype = 'p'
+ AND dep.classid = 'pg_class'::regclass
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
@@ -395,13 +408,15 @@ module ActiveRecord
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default)
clear_cache!
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
+ column = column_for(table_name, column_name)
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column
end
def change_column_null(table_name, column_name, null, default = nil)
clear_cache!
unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ column = column_for(table_name, column_name)
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 7e188907e1..764cb576d9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -7,6 +7,7 @@ require 'active_record/connection_adapters/postgresql/quoting'
require 'active_record/connection_adapters/postgresql/schema_statements'
require 'active_record/connection_adapters/postgresql/database_statements'
require 'active_record/connection_adapters/postgresql/referential_integrity'
+require 'active_record/connection_adapters/postgresql/column'
require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
@@ -43,222 +44,6 @@ module ActiveRecord
end
module ConnectionAdapters
- # PostgreSQL-specific extensions to column definitions in a table.
- class PostgreSQLColumn < Column #:nodoc:
- attr_accessor :array
-
- def initialize(name, default, oid_type, sql_type = nil, null = true)
- @oid_type = oid_type
- default_value = self.class.extract_value_from_default(default)
-
- if sql_type =~ /\[\]$/
- @array = true
- super(name, default_value, sql_type[0..sql_type.length - 3], null)
- else
- @array = false
- super(name, default_value, sql_type, null)
- end
-
- @default_function = default if has_default_function?(default_value, default)
- end
-
- def number?
- !array && super
- end
-
- def text?
- !array && super
- end
-
- # :stopdoc:
- class << self
- include ConnectionAdapters::PostgreSQLColumn::Cast
- include ConnectionAdapters::PostgreSQLColumn::ArrayParser
- attr_accessor :money_precision
- end
- # :startdoc:
-
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- return default unless default
-
- case default
- when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
- $1
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
- $1
- # Character types
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
- $1.gsub(/''/, "'")
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Hstore
- when /\A'(.*)'::hstore\z/
- $1
- # JSON
- when /\A'(.*)'::json\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- end
- end
-
- def type_cast_for_write(value)
- if @oid_type.respond_to?(:type_cast_for_write)
- @oid_type.type_cast_for_write(value)
- else
- super
- end
- end
-
- def type_cast(value)
- return if value.nil?
- return super if encoded?
-
- @oid_type.type_cast value
- end
-
- def accessor
- @oid_type.accessor
- end
-
- private
-
- def has_default_function?(default_value, default)
- !default_value && (%r{\w+\(.*\)} === default)
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- when /^timestamp/i; nil
- else super
- end
- end
-
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
- end
-
- # Extracts the precision from PostgreSQL-specific data types.
- def extract_precision(sql_type)
- if sql_type == 'money'
- self.class.money_precision
- elsif sql_type =~ /timestamp/i
- $1.to_i if sql_type =~ /\((\d+)\)/
- else
- super
- end
- end
-
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- case field_type
- # Numeric and monetary types
- when /^(?:real|double precision)$/
- :float
- # Monetary types
- when 'money'
- :decimal
- when 'hstore'
- :hstore
- when 'ltree'
- :ltree
- # Network address types
- when 'inet'
- :inet
- when 'cidr'
- :cidr
- when 'macaddr'
- :macaddr
- # Character types
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
- :string
- # Binary data types
- when 'bytea'
- :binary
- # Date/time types
- when /^timestamp with(?:out)? time zone$/
- :datetime
- when /^interval(?:|\(\d+\))$/
- :string
- # Geometric types
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
- :string
- # Bit strings
- when /^bit(?: varying)?(?:\(\d+\))?$/
- :string
- # XML type
- when 'xml'
- :xml
- # tsvector type
- when 'tsvector'
- :tsvector
- # Arrays
- when /^\D+\[\]$/
- :string
- # Object identifier types
- when 'oid'
- :integer
- # UUID type
- when 'uuid'
- :uuid
- # JSON type
- when 'json'
- :json
- # Small and big integer types
- when /^(?:small|big)int$/
- :integer
- when /(num|date|tstz|ts|int4|int8)range$/
- field_type.to_sym
- # Pass through all types that are not specific to PostgreSQL.
- else
- super
- end
- end
- end
-
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
#
# Options:
@@ -353,6 +138,10 @@ module ActiveRecord
def json(name, options = {})
column(name, 'json', options)
end
+
+ def citext(name, options = {})
+ column(name, 'citext', options)
+ end
end
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
@@ -393,6 +182,10 @@ module ActiveRecord
column name, type, options
end
+ def citext(name, options = {})
+ column(name, 'citext', options)
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -416,7 +209,7 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: "serial primary key",
- string: { name: "character varying", limit: 255 },
+ string: { name: "character varying" },
text: { name: "text" },
integer: { name: "integer" },
float: { name: "float" },
@@ -441,7 +234,8 @@ module ActiveRecord
macaddr: { name: "macaddr" },
uuid: { name: "uuid" },
json: { name: "json" },
- ltree: { name: "ltree" }
+ ltree: { name: "ltree" },
+ citext: { name: "citext" }
}
include Quoting
@@ -544,19 +338,15 @@ module ActiveRecord
end
end
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
+ @visitor = Arel::Visitors::PostgreSQL.new self
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::PostgreSQL.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
@connection_parameters, @config = connection_parameters, config
@@ -586,7 +376,8 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.connect_poll != PG::PGRES_POLLING_FAILED
+ @connection.query 'SELECT 1'
+ true
rescue PGError
false
end
@@ -600,7 +391,12 @@ module ActiveRecord
def reset!
clear_cache!
- super
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query 'ROLLBACK'
+ end
+ @connection.query 'DISCARD ALL'
+ configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
@@ -654,6 +450,10 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_materialized_views?
+ postgresql_version >= 90300
+ end
+
def enable_extension(name)
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
reload_type_map
@@ -755,6 +555,17 @@ module ActiveRecord
@type_map
end
+ def get_oid_type(oid, fmod, column_name)
+ if !type_map.key?(oid)
+ initialize_type_map(type_map, [oid])
+ end
+
+ type_map.fetch(oid, fmod) {
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
+ type_map[oid] = OID::Identity.new
+ }
+ end
+
def reload_type_map
type_map.clear
initialize_type_map(type_map)
@@ -779,19 +590,43 @@ module ActiveRecord
type_map
end
- def initialize_type_map(type_map)
- result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
- leaves, nodes = result.partition { |row| row['typelem'] == '0' }
+ def initialize_type_map(type_map, oids = nil)
+ if supports_ranges?
+ query = <<-SQL
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
+ FROM pg_type as t
+ LEFT JOIN pg_range as r ON oid = rngtypid
+ SQL
+ else
+ query = <<-SQL
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
+ FROM pg_type as t
+ SQL
+ end
- # populate the leaf nodes
+ if oids
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
+ end
+
+ result = execute(query, 'SCHEMA')
+ ranges, nodes = result.partition { |row| row['typtype'] == 'r' }
+ enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
+ domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
+ leaves, nodes = nodes.partition { |row| row['typelem'] == '0' }
+
+ # populate the enum types
+ enums.each do |row|
+ type_map[row['oid'].to_i] = OID::Enum.new
+ end
+
+ # populate the base types
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
type_map[row['oid'].to_i] = OID::NAMES[row['typname']]
end
records_by_oid = result.group_by { |row| row['oid'] }
- arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
-
# populate composite types
nodes.each do |row|
add_oid row, records_by_oid, type_map
@@ -802,10 +637,35 @@ module ActiveRecord
array = OID::Array.new type_map[row['typelem'].to_i]
type_map[row['oid'].to_i] = array
end
+
+ # populate range types
+ ranges.find_all { |row| type_map.key? row['rngsubtype'].to_i }.each do |row|
+ subtype = type_map[row['rngsubtype'].to_i]
+ range = OID::Range.new subtype
+ type_map[row['oid'].to_i] = range
+ end
+
+ # populate domain types
+ domains.each do |row|
+ base_type_oid = row["typbasetype"].to_i
+ if base_type = type_map[base_type_oid]
+ type_map[row['oid'].to_i] = base_type
+ else
+ warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}."
+ end
+ end
end
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
+ def execute_and_clear(sql, name, binds)
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
+ exec_cache(sql, name, binds)
+ ret = yield result
+ result.clear
+ ret
+ end
+
def exec_no_cache(sql, name, binds)
log(sql, name, binds) { @connection.async_exec(sql) }
end
@@ -853,7 +713,11 @@ module ActiveRecord
sql_key = sql_key(sql)
unless @statements.key? sql_key
nextkey = @statements.next_key
- @connection.prepare nextkey, sql
+ begin
+ @connection.prepare nextkey, sql
+ rescue => e
+ raise translate_exception_class(e, sql)
+ end
# Clear the queue
@connection.get_last_result
@statements[sql_key] = nextkey
@@ -879,9 +743,9 @@ module ActiveRecord
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
@@ -938,14 +802,6 @@ module ActiveRecord
exec_query(sql, name, binds)
end
- def select_raw(sql, name = nil)
- res = execute(sql, name)
- results = result_as_array(res)
- fields = res.fields
- res.clear
- return fields, results
- end
-
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index b40d7b3a58..2c6186774f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -30,12 +30,12 @@ module ActiveRecord
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
@@ -63,7 +63,7 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
- string: { name: "varchar", limit: 255 },
+ string: { name: "varchar" },
text: { name: "text" },
integer: { name: "integer" },
float: { name: "float" },
@@ -123,11 +123,7 @@ module ActiveRecord
end
end
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
- def initialize(connection, logger, config)
+ def initialize(connection, logger, connection_options, config)
super(connection, logger)
@active = nil
@@ -135,11 +131,12 @@ module ActiveRecord
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@config = config
+ @visitor = Arel::Visitors::SQLite.new self
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::SQLite.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
end
@@ -273,7 +270,7 @@ module ActiveRecord
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
end
class ExplainPrettyPrinter
@@ -350,8 +347,8 @@ module ActiveRecord
end
alias :create :insert_sql
- def select_rows(sql, name = nil)
- exec_query(sql, name).rows
+ def select_rows(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).rows
end
def begin_db_transaction #:nodoc:
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 11f6a47158..31e7390bf7 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -18,14 +18,14 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
+ # adapter: "sqlite3",
# database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
+ # "adapter" => "sqlite3",
# "database" => "path/to/dbfile"
# )
#
@@ -58,9 +58,9 @@ module ActiveRecord
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
- def initialize(raw_configurations, url = ENV['DATABASE_URL'])
+ def initialize(raw_configurations)
@raw_config = raw_configurations.dup
- @url = url
+ @env = DEFAULT_ENV.call.to_s
end
# Returns fully resolved connection hashes.
@@ -71,38 +71,11 @@ module ActiveRecord
private
def config
- if @url
- raw_merged_into_default
- else
- @raw_config
- end
- end
-
- def raw_merged_into_default
- default = default_url_hash
-
- @raw_config.each do |env, values|
- default[env] = values || {}
- default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash)
- end
- default
- end
-
- # When the raw configuration is not present and ENV['DATABASE_URL']
- # is available we return a hash with the connection information in
- # the connection URL. This hash responds to any string key with
- # resolved connection information.
- def default_url_hash
- if @raw_config.blank?
- Hash.new do |hash, key|
- hash[key] = if key.is_a? String
- ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
- else
- nil
- end
+ @raw_config.dup.tap do |cfg|
+ if url = ENV['DATABASE_URL']
+ cfg[@env] ||= {}
+ cfg[@env]["url"] ||= url
end
- else
- {}
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index cd8690d500..4571cc0786 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -76,6 +76,15 @@ module ActiveRecord
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
+ ##
+ # :singleton-method:
+ # Specify whether schema dump should happen at the end of the
+ # db:migrate rake task. This is true by default, which is useful for the
+ # development environment. This should ideally be false in the production
+ # environment where dumping schema is rarely needed.
+ mattr_accessor :dump_schema_after_migration, instance_writer: false
+ self.dump_schema_after_migration = true
+
# :nodoc:
mattr_accessor :maintain_test_schema, instance_accessor: false
@@ -85,6 +94,7 @@ module ActiveRecord
end
class_attribute :default_connection_handler, instance_writer: false
+ class_attribute :find_by_statement_cache
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -98,6 +108,71 @@ module ActiveRecord
end
module ClassMethods
+ def initialize_find_by_cache
+ self.find_by_statement_cache = {}.extend(Mutex_m)
+ end
+
+ def inherited(child_class)
+ child_class.initialize_find_by_cache
+ super
+ end
+
+ def find(*ids)
+ # We don't have cache keys for this stuff yet
+ return super unless ids.length == 1
+ return super if block_given? ||
+ primary_key.nil? ||
+ default_scopes.any? ||
+ columns_hash.include?(inheritance_column) ||
+ ids.first.kind_of?(Array)
+
+ id = ids.first
+ if ActiveRecord::Base === id
+ id = id.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
+ "Please pass the id of the object by calling `.id`"
+ end
+ key = primary_key
+
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
+ where(key => params.bind).limit(1)
+ }
+ }
+ record = s.execute([id], self, connection).first
+ unless record
+ raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
+ end
+ record
+ end
+
+ def find_by(*args)
+ return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
+
+ hash = args.first
+
+ return super if hash.values.any? { |v|
+ v.nil? || Array === v || Hash === v
+ }
+
+ key = hash.keys
+
+ klass = self
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
+ wheres = key.each_with_object({}) { |param,o|
+ o[param] = params.bind
+ }
+ klass.where(wheres).limit(1)
+ }
+ }
+ begin
+ s.execute(hash.values, self, connection).first
+ rescue TypeError => e
+ raise ActiveRecord::StatementInvalid.new(e.message, e)
+ end
+ end
+
def initialize_generated_modules
super
@@ -138,12 +213,12 @@ module ActiveRecord
# class Post < ActiveRecord::Base
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
- def arel_table
+ def arel_table # :nodoc:
@arel_table ||= Arel::Table.new(table_name, arel_engine)
end
# Returns the Arel engine.
- def arel_engine
+ def arel_engine # :nodoc:
@arel_engine ||=
if Base == self || connection_handler.retrieve_connection_pool(self)
self
@@ -182,9 +257,7 @@ module ActiveRecord
@column_types = self.class.column_types
init_internals
- init_changed_attributes
- ensure_proper_type
- populate_with_current_scope_attributes
+ initialize_internals_callback
# +options+ argument is only needed to make protected_attributes gem easier to hook.
# Remove it when we drop support to this gem.
@@ -255,16 +328,13 @@ module ActiveRecord
run_callbacks(:initialize) unless _initialize_callbacks.empty?
- @changed_attributes = {}
- init_changed_attributes
-
@aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@new_record = true
+ @destroyed = false
- ensure_proper_type
super
end
@@ -281,7 +351,7 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- coder['attributes'] = attributes
+ coder['attributes'] = attributes_for_coder
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -296,7 +366,7 @@ module ActiveRecord
def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
- id &&
+ !id.nil? &&
comparison_object.id == id
end
alias :eql? :==
@@ -397,13 +467,10 @@ module ActiveRecord
end
def update_attributes_from_transaction_state(transaction_state, depth)
- if transaction_state && !has_transactional_callbacks?
+ if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
unless @reflects_state[depth]
- if transaction_state.committed?
- committed!
- elsif transaction_state.rolledback?
- rolledback!
- end
+ restore_transaction_record_state if transaction_state.rolledback?
+ clear_transaction_record_state
@reflects_state[depth] = true
end
@@ -443,13 +510,7 @@ module ActiveRecord
@reflects_state = [false]
end
- def init_changed_attributes
- # Intentionally avoid using #column_defaults since overridden defaults (as is done in
- # optimistic locking) won't get written unless they get marked as changed
- self.class.columns.each do |c|
- attr, orig_value = c.name, c.default
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
- end
+ def initialize_internals_callback
end
# This method is needed to make protected_attributes gem easier to hook.
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index dcbdf75627..b7b790322a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -118,5 +118,54 @@ module ActiveRecord
update_counters(id, counter_name => -1)
end
end
+
+ protected
+
+ def actually_destroyed?
+ @_actually_destroyed
+ end
+
+ def clear_destroy_state
+ @_actually_destroyed = nil
+ end
+
+ private
+
+ def _create_record(*)
+ id = super
+
+ each_counter_cached_associations do |association|
+ if send(association.reflection.name)
+ association.increment_counters
+ @_after_create_counter_called = true
+ end
+ end
+
+ id
+ end
+
+ def destroy_row
+ affected_rows = super
+
+ if affected_rows > 0
+ each_counter_cached_associations do |association|
+ foreign_key = association.reflection.foreign_key.to_sym
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
+ if send(association.reflection.name)
+ association.decrement_counters
+ end
+ end
+ end
+ end
+
+ affected_rows
+ end
+
+ def each_counter_cached_associations
+ reflections.each do |name, reflection|
+ yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ end
+ end
+
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 5caab09038..e94b74063e 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -6,8 +6,12 @@ module ActiveRecord
# then we can remove the indirection.
def respond_to?(name, include_private = false)
- match = Method.match(self, name)
- match && match.valid? || super
+ if self == Base
+ super
+ else
+ match = Method.match(self, name)
+ match && match.valid? || super
+ end
end
private
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 3deb2d65f8..18f1ca26de 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/deep_dup'
+
module ActiveRecord
# Declare an enum attribute where the values map to integers in the database,
# but can be queried by name. Example:
@@ -62,7 +64,19 @@ module ActiveRecord
# Use that class method when you need to know the ordinal value of an enum:
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
+ #
+ # Where conditions on an enum attribute must use the ordinal value of an enum.
module Enum
+ def self.extended(base)
+ base.class_attribute(:defined_enums)
+ base.defined_enums = {}
+ end
+
+ def inherited(base)
+ base.defined_enums = defined_enums.deep_dup
+ super
+ end
+
def enum(definitions)
klass = self
definitions.each do |name, values|
@@ -71,10 +85,12 @@ module ActiveRecord
name = name.to_sym
# def self.statuses statuses end
+ detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
_enum_methods_module.module_eval do
# def status=(value) self[:status] = statuses[value] end
+ klass.send(:detect_enum_conflict!, name, "#{name}=")
define_method("#{name}=") { |value|
if enum_values.has_key?(value) || value.blank?
self[name] = enum_values[value]
@@ -89,35 +105,95 @@ module ActiveRecord
}
# def status() statuses.key self[:status] end
+ klass.send(:detect_enum_conflict!, name, name)
define_method(name) { enum_values.key self[name] }
# def status_before_type_cast() statuses.key self[:status] end
+ klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
define_method("#{name}_before_type_cast") { enum_values.key self[name] }
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
enum_values[value] = i
- # scope :active, -> { where status: 0 }
- klass.scope value, -> { klass.where name => i }
-
# def active?() status == 0 end
+ klass.send(:detect_enum_conflict!, name, "#{value}?")
define_method("#{value}?") { self[name] == i }
# def active!() update! status: :active end
+ klass.send(:detect_enum_conflict!, name, "#{value}!")
define_method("#{value}!") { update! name => value }
+
+ # scope :active, -> { where status: 0 }
+ klass.send(:detect_enum_conflict!, name, value, true)
+ klass.scope value, -> { klass.where name => i }
end
end
+ defined_enums[name.to_s] = enum_values
end
end
private
def _enum_methods_module
@_enum_methods_module ||= begin
- mod = Module.new
+ mod = Module.new do
+ private
+ def save_changed_attribute(attr_name, value)
+ if (mapping = self.class.defined_enums[attr_name.to_s])
+ if attribute_changed?(attr_name)
+ old = changed_attributes[attr_name]
+
+ if mapping[old] == value
+ changed_attributes.delete(attr_name)
+ end
+ else
+ old = clone_attribute_value(:read_attribute, attr_name)
+
+ if old != value
+ changed_attributes[attr_name] = mapping.key old
+ end
+ end
+ else
+ super
+ end
+ end
+ end
include mod
mod
end
end
+
+ ENUM_CONFLICT_MESSAGE = \
+ "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
+ "this will generate a %{type} method \"%{method}\", which is already defined " \
+ "by %{source}."
+
+ def detect_enum_conflict!(enum_name, method_name, klass_method = false)
+ if klass_method && dangerous_class_method?(method_name)
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
+ enum: enum_name,
+ klass: self.name,
+ type: 'class',
+ method: method_name,
+ source: 'Active Record'
+ }
+ elsif !klass_method && dangerous_attribute_method?(method_name)
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
+ enum: enum_name,
+ klass: self.name,
+ type: 'instance',
+ method: method_name,
+ source: 'Active Record'
+ }
+ elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
+ enum: enum_name,
+ klass: self.name,
+ type: 'instance',
+ method: method_name,
+ source: 'another enum'
+ }
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 7f6228131f..71efbb8f93 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -95,15 +95,7 @@ module ActiveRecord
end
# Raised when a given database does not exist
- class NoDatabaseError < ActiveRecordError
- def initialize(message)
- super extend_message(message)
- end
-
- # can be over written to add additional error information.
- def extend_message(message)
- message
- end
+ class NoDatabaseError < StatementInvalid
end
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index fee3f51d9e..47d32fae05 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -2,6 +2,7 @@ require 'erb'
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
+require 'active_support/core_ext/securerandom'
require 'active_record/fixture_set/file'
require 'active_record/errors'
@@ -361,6 +362,7 @@ module ActiveRecord
# geeksomnia:
# name: Geeksomnia's Account
# subdomain: $LABEL
+ # email: $LABEL@email.com
#
# Also, sometimes (like when porting older join table fixtures) you'll need
# to be able to get a hold of the identifier for a given label. ERB
@@ -462,7 +464,7 @@ module ActiveRecord
@class_names.delete_if { |k,klass|
unless klass.is_a? Class
klass = klass.safe_constantize
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `set_fixture_class` will be removed in Rails 4.2. Use the class itself instead.")
end
!insert_class(@class_names, k, klass)
}
@@ -549,9 +551,13 @@ module ActiveRecord
end
# Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
+ def self.identify(label, column_type = :integer)
+ if column_type == :uuid
+ SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s)
+ else
+ Zlib.crc32(label.to_s) % MAX_ID
+ end
end
# Superclass for the evaluation contexts used by ERB fixtures.
@@ -568,7 +574,7 @@ module ActiveRecord
@model_class = nil
if class_name.is_a?(String)
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `FixtureSet.new` will be removed in Rails 4.2. Use the class itself instead.")
end
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@@ -627,12 +633,12 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = label if "$LABEL" == value
+ row[key] = value.gsub("$LABEL", label) if value.is_a?(String)
end
# generate a primary key if necessary
if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = ActiveRecord::FixtureSet.identify(label)
+ row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
end
# If STI is used, find the correct subclass for association reflection
@@ -655,7 +661,8 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- row[fk_name] = ActiveRecord::FixtureSet.identify(value)
+ fk_type = association.active_record.columns_hash[association.foreign_key].type
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
if association.options[:through]
@@ -682,6 +689,10 @@ module ActiveRecord
def name
@association.name
end
+
+ def primary_key_type
+ @association.klass.column_types[@association.klass.primary_key].type
+ end
end
class HasManyThroughProxy < ReflectionProxy # :nodoc:
@@ -699,17 +710,22 @@ module ActiveRecord
@primary_key_name ||= model_class && model_class.primary_key
end
+ def primary_key_type
+ @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
+ end
+
def add_join_records(rows, row, association)
# This is the case when the join table has no fixtures file
if (targets = row.delete(association.name.to_s))
- table_name = association.join_table
- lhs_key = association.lhs_key
- rhs_key = association.rhs_key
+ table_name = association.join_table
+ column_type = association.primary_key_type
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
rows[table_name].concat targets.map { |target|
{ lhs_key => row[primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target) }
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
}
end
end
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
new file mode 100644
index 0000000000..4a7aace460
--- /dev/null
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index da73112e90..08fc91c9df 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -195,8 +195,18 @@ module ActiveRecord
end
end
+ def initialize_dup(other)
+ super
+ ensure_proper_type
+ end
+
private
+ def initialize_internals_callback
+ super
+ ensure_proper_type
+ end
+
# Sets the attribute used for single table inheritance to this class name if this is not the
# ActiveRecord::Base descendant.
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 6f54729b3c..4d63b04d9f 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -66,7 +66,7 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
- def update_record(attribute_names = @attributes.keys) #:nodoc:
+ def _update_record(attribute_names = @attributes.keys) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index b57da73969..b6b02322d7 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -385,8 +385,8 @@ module ActiveRecord
attr_accessor :delegate # :nodoc:
attr_accessor :disable_ddl_transaction # :nodoc:
- def check_pending!
- raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
+ def check_pending!(connection = Base.connection)
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
end
def load_schema_if_pending!
@@ -830,17 +830,17 @@ module ActiveRecord
SchemaMigration.all.map { |x| x.version.to_i }.sort
end
- def current_version
+ def current_version(connection = Base.connection)
sm_table = schema_migrations_table_name
- if Base.connection.table_exists?(sm_table)
+ if connection.table_exists?(sm_table)
get_all_versions.max || 0
else
0
end
end
- def needs_migration?
- current_version < last_version
+ def needs_migration?(connection = Base.connection)
+ current_version(connection) < last_version
end
def last_version
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 9139ad953c..c44d8c1665 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -140,7 +140,12 @@ module ActiveRecord
def invert_add_index(args)
table, columns, options = *args
- [:remove_index, [table, (options || {}).merge(column: columns)]]
+ options ||= {}
+
+ index_name = options[:name]
+ options_hash = index_name ? { name: index_name } : { column: columns }
+
+ [:remove_index, [table, options_hash]]
end
def invert_remove_index(args)
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 9d92e747d4..e6195e48a5 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -485,10 +485,10 @@ module ActiveRecord
end
# Takes in a limit and checks if the attributes_collection has too many
- # records. The method will take limits in the form of symbols, procs, and
- # number-like objects (anything that can be compared with an integer).
+ # records. It accepts limit in the form of symbol, proc, or
+ # number-like object (anything that can be compared with an integer).
#
- # Will raise an TooManyRecords error if the attributes_collection is
+ # Raises TooManyRecords error if the attributes_collection is
# larger than the limit.
def check_record_limit!(limit, attributes_collection)
if limit
@@ -519,7 +519,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be rejected by checking
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 5b255c3fe5..05d0c41678 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
def count(*)
- 0
+ calculate :count, nil
end
def sum(*)
@@ -54,7 +54,7 @@ module ActiveRecord
# TODO: Remove _options argument as soon we remove support to
# activerecord-deprecated_finders.
if operation == :count
- 0
+ group_values.any? ? Hash.new : 0
else
nil
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 460fbdb3f8..13d7432773 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
# Returns true if this object hasn't been saved yet -- that is, a record
- # for the object doesn't exist in the data store yet; otherwise, returns false.
+ # for the object doesn't exist in the database yet; otherwise, returns false.
def new_record?
sync_with_transaction_state
@new_record
@@ -214,6 +214,8 @@ module ActiveRecord
#
# This method raises an +ActiveRecord::ActiveRecordError+ if the
# attribute is marked as readonly.
+ #
+ # See also +update_column+.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
@@ -389,7 +391,7 @@ module ActiveRecord
fresh_object =
if options && options[:lock]
- self.class.unscoped { self.class.lock.find(id) }
+ self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
@@ -403,15 +405,18 @@ module ActiveRecord
end
# Saves the record with the updated_at/on attributes set to the current time.
- # Please note that no validation is performed and only the +after_touch+
- # callback is executed.
- # If an attribute name is passed, that attribute is updated along with
- # updated_at/on attributes.
+ # Please note that no validation is performed and only the +after_touch+,
+ # +after_commit+ and +after_rollback+ callbacks are executed.
+ #
+ # If attribute names are passed, they are updated along with updated_at/on
+ # attributes.
#
- # product.touch # updates updated_at/on
- # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
#
- # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
+ # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on
+ # associated object.
#
# class Brake < ActiveRecord::Base
# belongs_to :car, touch: true
@@ -430,11 +435,11 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(name = nil)
+ def touch(*names)
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
attributes = timestamp_attributes_for_update_in_model
- attributes << name if name
+ attributes.concat(names)
unless attributes.empty?
current_time = current_time_from_proper_timezone
@@ -450,6 +455,8 @@ module ActiveRecord
changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
+ else
+ true
end
end
@@ -477,24 +484,24 @@ module ActiveRecord
def create_or_update
raise ReadOnlyRecord if readonly?
- result = new_record? ? create_record : update_record
+ result = new_record? ? _create_record : _update_record
result != false
end
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
- def update_record(attribute_names = @attributes.keys)
+ def _update_record(attribute_names = @attributes.keys)
attributes_values = arel_attributes_with_values_for_update(attribute_names)
if attributes_values.empty?
0
else
- self.class.unscoped.update_record attributes_values, id, id_was
+ self.class.unscoped._update_record attributes_values, id, id_was
end
end
# Creates a record with values matching those of the instance attributes
# and returns its id.
- def create_record(attribute_names = @attributes.keys)
+ def _create_record(attribute_names = @attributes.keys)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index fd4c973504..ef138c6f80 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,6 +1,7 @@
module ActiveRecord
module Querying
delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
+ delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 11b564f8f9..a4ceacbf44 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -116,17 +116,22 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
+ self.configurations = Rails.application.config.database_configuration
- class ActiveRecord::NoDatabaseError
- remove_possible_method :extend_message
- def extend_message(message)
- message << "Run `$ bin/rake db:create db:migrate` to create your database"
- message
- end
- end
+ begin
+ establish_connection
+ rescue ActiveRecord::NoDatabaseError
+ warn <<-end_warning
+Oops - You have a database configured, but it doesn't exist yet!
- self.configurations = Rails.application.config.database_configuration
- establish_connection
+Here's how to get started:
+
+ 1. Configure your database in config/database.yml.
+ 2. Run `bin/rake db:create` to create the database.
+ 3. Run `bin/rake db:setup` to load your database schema.
+end_warning
+ raise
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 561387a179..6b0459ea37 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -34,7 +34,7 @@ db_namespace = namespace :db do
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
end
- db_namespace['_dump'].invoke
+ db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
end
task :_dump do
@@ -75,7 +75,7 @@ db_namespace = namespace :db do
# desc 'Runs the "down" for a given migration VERSION.'
task :down => [:environment, :load_config] do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
- raise 'VERSION is required' unless version
+ raise 'VERSION is required - To go down one migration, run db:rollback' unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
db_namespace['_dump'].invoke
end
@@ -186,7 +186,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(',') : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -268,7 +268,8 @@ db_namespace = namespace :db do
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- if ActiveRecord::Base.connection.supports_migrations?
+ if ActiveRecord::Base.connection.supports_migrations? &&
+ ActiveRecord::SchemaMigration.table_exists?
File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information
f.print "\n"
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index bce7766501..95485ddada 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,3 +1,5 @@
+require 'thread'
+
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
@@ -22,11 +24,11 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name => reflection)
+ ar.reflections = ar.reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
# \Reflection enables to interrogate Active Record classes and objects
@@ -48,7 +50,7 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- aggregate_reflections[aggregation]
+ aggregate_reflections[aggregation.to_s]
end
# Returns an array of AssociationReflection objects for all the
@@ -72,7 +74,7 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflections[association]
+ reflections[association.to_s]
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -151,7 +153,7 @@ module ActiveRecord
super ||
other_aggregation.kind_of?(self.class) &&
name == other_aggregation.name &&
- other_aggregation.options &&
+ !other_aggregation.options.nil? &&
active_record == other_aggregation.active_record
end
@@ -199,6 +201,18 @@ module ActiveRecord
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
+ @association_scope_cache = {}
+ @scope_lock = Mutex.new
+ end
+
+ def association_scope_cache(conn, owner)
+ key = conn.prepared_statements
+ if options[:polymorphic]
+ key = [key, owner.read_attribute(@foreign_type)]
+ end
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
+ @association_scope_cache[key] ||= yield
+ }
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -449,9 +463,9 @@ module ActiveRecord
end
def derive_class_name
- class_name = name.to_s.camelize
+ class_name = name.to_s
class_name = class_name.singularize if collection?
- class_name
+ class_name.camelize
end
def derive_foreign_key
@@ -617,7 +631,7 @@ module ActiveRecord
# # => [:tag, :tags]
#
def source_reflection_names
- (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
end
def source_reflection_name # :nodoc:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 745c6cf349..24b33ab0a8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+require 'arel/collectors/bind'
module ActiveRecord
# = Active Record Relation
@@ -24,6 +25,7 @@ module ActiveRecord
@klass = klass
@table = table
@values = values
+ @offsets = {}
@loaded = false
end
@@ -69,7 +71,7 @@ module ActiveRecord
binds)
end
- def update_record(values, id, id_was) # :nodoc:
+ def _update_record(values, id, id_was) # :nodoc:
substitutes, binds = substitute_values values
um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
@@ -222,6 +224,7 @@ module ActiveRecord
# Please see further details in the
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
+ #TODO: Fix for binds.
exec_explain(collecting_queries_for_explain { exec_queries })
end
@@ -237,7 +240,7 @@ module ActiveRecord
# Returns size of the records.
def size
- loaded? ? @records.length : count
+ loaded? ? @records.length : count(:all)
end
# Returns true if there are no records.
@@ -247,8 +250,7 @@ module ActiveRecord
if limit_value == 0
true
else
- # FIXME: This count is not compatible with #select('authors.*') or other select narrows
- c = count
+ c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
end
@@ -323,7 +325,8 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- @klass.connection.update stmt, 'SQL', bind_values
+ bvs = bind_values + arel.bind_values
+ @klass.connection.update stmt, 'SQL', bvs
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -495,9 +498,10 @@ module ActiveRecord
end
def reset
- @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
+ @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
@records = []
+ @offsets = {}
self
end
@@ -515,11 +519,11 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
- ast = relation.arel.ast
- binds = relation.bind_values.dup
- visitor.accept(ast) do
- connection.quote(*binds.shift.reverse)
- end
+ arel = relation.arel
+ binds = (arel.bind_values + relation.bind_values).dup
+ binds.map! { |bv| connection.quote(*bv.reverse) }
+ collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
+ collect.substitute_binds(binds).join
end
end
@@ -527,17 +531,22 @@ module ActiveRecord
#
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
- def where_values_hash
+ def where_values_hash(relation_table_name = table_name)
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
- node.left.relation.name == table_name
+ node.left.relation.name == relation_table_name
}
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
- binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }])
Hash[equalities.map { |where|
name = where.left.name
- [name, binds.fetch(name.to_s) { where.right }]
+ [name, binds.fetch(name.to_s) {
+ case where.right
+ when Array then where.right.map(&:val)
+ else
+ where.right.val
+ end
+ }]
}]
end
@@ -569,6 +578,8 @@ module ActiveRecord
# Compares two relations for equality.
def ==(other)
case other
+ when Associations::CollectionProxy, AssociationRelation
+ self == other.to_a
when Relation
other.to_sql == to_sql
when Array
@@ -599,7 +610,7 @@ module ActiveRecord
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
preload = preload_values
preload += includes_values unless eager_loading?
@@ -616,7 +627,9 @@ module ActiveRecord
def references_eager_loaded_tables?
joined_tables = arel.join_sources.map do |join|
- unless join.is_a?(Arel::Nodes::StringJoin)
+ if join.is_a?(Arel::Nodes::StringJoin)
+ tables_in_string(join.left)
+ else
[join.left.table_name, join.left.table_alias]
end
end
@@ -628,5 +641,12 @@ module ActiveRecord
(references_values - joined_tables).any?
end
+
+ def tables_in_string(string)
+ 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_']
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 49b01909c6..29fc150b3d 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -52,7 +52,9 @@ module ActiveRecord
records.each { |record| yield record }
end
else
- enum_for :find_each, options
+ enum_for :find_each, options do
+ options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
+ end
end
end
@@ -64,6 +66,16 @@ module ActiveRecord
# group.each { |person| person.party_all_night! }
# end
#
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # Person.find_in_batches.with_index do |group, batch|
+ # puts "Processing group ##{batch}"
+ # group.each(&:recover_from_last_night!)
+ # end
+ #
+ # To be yielded each record one by one, use #find_each instead.
+ #
# ==== 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.
@@ -88,30 +100,33 @@ module ActiveRecord
options.assert_valid_keys(:start, :batch_size)
relation = self
+ start = options[:start]
+ batch_size = options[:batch_size] || 1000
+
+ unless block_given?
+ return to_enum(:find_in_batches, options) do
+ total = start ? where(table[primary_key].gteq(start)).size : size
+ (total - 1).div(batch_size) + 1
+ end
+ end
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- start = options.delete(:start)
- batch_size = options.delete(:batch_size) || 1000
-
relation = relation.reorder(batch_order).limit(batch_size)
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
while records.any?
records_size = records.size
primary_key_offset = records.last.id
+ raise "Primary key not included in the custom select clause" unless primary_key_offset
yield records
break if records_size < batch_size
- if primary_key_offset
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
- else
- raise "Primary key not included in the custom select clause"
- end
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 45ffb99868..0b56430b34 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -188,7 +188,7 @@ module ActiveRecord
private
def has_include?(column_name)
- eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
end
def perform_calculation(operation, column_name, options = {})
@@ -231,15 +231,18 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = reorder(nil)
+ relation = unscope(:order)
column_alias = column_name
+ bind_values = nil
+
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
query_builder = build_count_subquery(relation, column_name, distinct)
+ bind_values = relation.bind_values
else
column = aggregate_column(column_name)
@@ -249,9 +252,10 @@ module ActiveRecord
relation.select_values = [select_value]
query_builder = relation.arel
+ bind_values = query_builder.bind_values + relation.bind_values
end
- result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
+ result = @klass.connection.select_all(query_builder, nil, bind_values)
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 21beed332f..9c666dcd3b 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -40,7 +40,7 @@ module ActiveRecord
BLACKLISTED_ARRAY_METHODS = [
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
- :keep_if, :pop, :shift, :delete_at, :compact
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!
].to_set # :nodoc:
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, to: :to_a
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4984dbd277..db32ae12a8 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
module ActiveRecord
module FinderMethods
ONE_AS_ONE = '1 AS one'
@@ -127,9 +129,9 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_first_with_limit(limit)
+ find_nth_with_limit(offset_index, limit)
else
- find_first
+ find_nth(0, offset_index)
end
end
@@ -172,6 +174,86 @@ module ActiveRecord
last or raise RecordNotFound
end
+ # Find the second record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.second # returns the second object fetched by SELECT * FROM people
+ # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
+ # Person.where(["user_name = :u", { u: user_name }]).second
+ def second
+ find_nth(1, offset_index)
+ end
+
+ # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def second!
+ second or raise RecordNotFound
+ end
+
+ # Find the third record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.third # returns the third object fetched by SELECT * FROM people
+ # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
+ # Person.where(["user_name = :u", { u: user_name }]).third
+ def third
+ find_nth(2, offset_index)
+ end
+
+ # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def third!
+ third or raise RecordNotFound
+ end
+
+ # Find the fourth record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
+ # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
+ # Person.where(["user_name = :u", { u: user_name }]).fourth
+ def fourth
+ find_nth(3, offset_index)
+ end
+
+ # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def fourth!
+ fourth or raise RecordNotFound
+ end
+
+ # Find the fifth record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
+ # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
+ # Person.where(["user_name = :u", { u: user_name }]).fifth
+ def fifth
+ find_nth(4, offset_index)
+ end
+
+ # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def fifth!
+ fifth or raise RecordNotFound
+ end
+
+ # Find the forty-second record. Also known as accessing "the reddit".
+ # If no order is defined it will order by primary key.
+ #
+ # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
+ # Person.where(["user_name = :u", { u: user_name }]).forty_two
+ def forty_two
+ find_nth(41, offset_index)
+ end
+
+ # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found.
+ def forty_two!
+ forty_two or raise RecordNotFound
+ end
+
# Returns +true+ if a record exists in the table that matches the +id+ or
# conditions given, or +false+ otherwise. The argument can take six forms:
#
@@ -200,7 +282,12 @@ module ActiveRecord
# Person.exists?(false)
# Person.exists?
def exists?(conditions = :none)
- conditions = conditions.id if Base === conditions
+ if Base === conditions
+ conditions = conditions.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `exists?`." \
+ "Please pass the id of the object by calling `.id`"
+ end
+
return false if !conditions
relation = apply_join_dependency(self, construct_join_dependency)
@@ -212,7 +299,9 @@ module ActiveRecord
when Array, Hash
relation = relation.where(conditions)
else
- relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
+ unless conditions == :none
+ relation = where(primary_key => conditions)
+ end
end
connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
@@ -231,9 +320,9 @@ module ActiveRecord
conditions = " [#{conditions}]" if conditions
if Array(ids).size == 1
- error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
else
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
end
@@ -242,6 +331,10 @@ module ActiveRecord
private
+ def offset_index
+ offset_value || 0
+ end
+
def find_with_associations
join_dependency = construct_join_dependency
@@ -255,7 +348,8 @@ module ActiveRecord
if ActiveRecord::NullRelation === relation
[]
else
- rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
+ arel = relation.arel
+ rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
join_dependency.instantiate(rows, aliases)
end
end
@@ -267,7 +361,15 @@ module ActiveRecord
end
def construct_relation_for_association_calculations
- apply_join_dependency(self, construct_join_dependency(arel.froms.first))
+ from = arel.froms.first
+ if Arel::Table === from
+ apply_join_dependency(self, construct_join_dependency)
+ else
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
+ # There are no tests that test this branch, but presumably it's
+ # possible for `from` to be a list?
+ apply_join_dependency(self, construct_join_dependency(from))
+ end
end
def apply_join_dependency(relation, join_dependency)
@@ -321,7 +423,11 @@ module ActiveRecord
end
def find_one(id)
- id = id.id if ActiveRecord::Base === id
+ if ActiveRecord::Base === id
+ id = id.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
+ "Please pass the id of the object by calling `.id`"
+ end
column = columns_hash[primary_key]
substitute = connection.substitute_at(column, bind_values.length)
@@ -364,20 +470,24 @@ module ActiveRecord
end
end
- def find_first
+ def find_nth(index, offset)
if loaded?
- @records.first
+ @records[index]
else
- @first ||= find_first_with_limit(1).first
+ offset += index
+ @offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
- def find_first_with_limit(limit)
- if order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(limit).to_a
- else
- limit(limit).to_a
- end
+ def find_nth_with_limit(offset, limit)
+ relation = if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc)
+ else
+ self
+ end
+
+ relation = relation.offset(offset) unless offset.zero?
+ relation.limit(limit).to_a
end
def find_last
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 182b9ed89c..fcb28a18f6 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -30,6 +30,8 @@ module ActiveRecord
else
other.joins!(*v)
end
+ elsif k == :select
+ other._select!(v)
else
other.send("#{k}!", v)
end
@@ -62,7 +64,13 @@ module ActiveRecord
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
# don't fall through the cracks.
- relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
+ unless value.nil? || (value.blank? && false != value)
+ if name == :select
+ relation._select!(*value)
+ else
+ relation.send("#{name}!", *value)
+ end
+ end
end
merge_multi_values
@@ -139,7 +147,6 @@ module ActiveRecord
def merge_single_values
relation.from_value = values[:from] unless relation.from_value
relation.lock_value = values[:lock] unless relation.lock_value
- relation.reverse_order_value = values[:reverse_order]
unless values[:create_with].blank?
relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 1252af7635..d40f276968 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -113,13 +113,14 @@ module ActiveRecord
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new)
- private
- def self.build(attribute, value)
- handler_for(value).call(attribute, value)
- end
+ def self.build(attribute, value)
+ handler_for(value).call(attribute, value)
+ end
+ private_class_method :build
- def self.handler_for(object)
- @handlers.detect { |klass, _| klass === object }.last
- end
+ def self.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/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 979216bee7..416f2305d2 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -49,6 +49,8 @@ module ActiveRecord
Arel::Nodes::Not.new(rel)
end
end
+
+ @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
@scope.where_values += where_value
@scope
end
@@ -62,6 +64,7 @@ module ActiveRecord
#
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
CODE
@@ -79,11 +82,22 @@ module ActiveRecord
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
@values[:#{name}] = value # @values[:readonly] = value
end # end
CODE
end
+ def check_cached_relation # :nodoc:
+ if defined?(@arel) && @arel
+ @arel = nil
+ ActiveSupport::Deprecation.warn <<-WARNING
+Modifying already cached Relation. The cache will be reset.
+Use a cloned Relation to prevent this warning.
+WARNING
+ end
+ end
+
def create_with_value # :nodoc:
@values[:create_with] || {}
end
@@ -120,6 +134,9 @@ module ActiveRecord
# Will throw an error, but this will work:
#
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
+ #
+ # Note that +includes+ works with association names while +references+ needs
+ # the actual table name.
def includes(*args)
check_if_method_has_arguments!(:includes, args)
spawn.includes!(*args)
@@ -163,24 +180,26 @@ module ActiveRecord
self
end
- # Used to indicate that an association is referenced by an SQL string, and should
- # therefore be JOINed in any query rather than loaded separately.
+ # Use to indicate that the given +table_names+ are referenced by an SQL string,
+ # and should therefore be JOINed in any query rather than loaded separately.
+ # This method only works in conjunction with +includes+.
+ # See #includes for more details.
#
# User.includes(:posts).where("posts.name = 'foo'")
# # => Doesn't JOIN the posts table, resulting in an error.
#
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# # => Query now knows the string references posts, so adds a JOIN
- def references(*args)
- check_if_method_has_arguments!(:references, args)
- spawn.references!(*args)
+ def references(*table_names)
+ check_if_method_has_arguments!(:references, table_names)
+ spawn.references!(*table_names)
end
- def references!(*args) # :nodoc:
- args.flatten!
- args.map!(&:to_s)
+ def references!(*table_names) # :nodoc:
+ table_names.flatten!
+ table_names.map!(&:to_s)
- self.references_values |= args
+ self.references_values |= table_names
self
end
@@ -197,7 +216,7 @@ module ActiveRecord
# fields are retrieved:
#
# Model.select(:field)
- # # => [#<Model field:value>]
+ # # => [#<Model id: nil, field: "value">]
#
# Although in the above example it looks as though this method returns an
# array, it actually returns a relation object and can have other query
@@ -206,12 +225,12 @@ module ActiveRecord
# The argument to the method can also be an array of fields.
#
# Model.select(:field, :other_field, :and_one_more)
- # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
#
# You can also use one or more strings, which will be used unchanged as SELECT fields.
#
# Model.select('field AS field_one', 'other_field AS field_two')
- # # => [#<Model field: "value", other_field: "value">]
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
#
# If an alias was specified, it will be accessible from the resulting objects:
#
@@ -219,7 +238,7 @@ module ActiveRecord
# # => "value"
#
# Accessing attributes of an object that do not have fields retrieved by a select
- # will throw <tt>ActiveModel::MissingAttributeError</tt>:
+ # except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
# Model.select(:field).first.other_field
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
@@ -228,13 +247,15 @@ module ActiveRecord
to_a.select { |*block_args| yield(*block_args) }
else
raise ArgumentError, 'Call this with at least one field' if fields.empty?
- spawn.select!(*fields)
+ spawn._select!(*fields)
end
end
- def select!(*fields) # :nodoc:
+ def _select!(*fields) # :nodoc:
fields.flatten!
-
+ fields.map! do |field|
+ klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
+ end
self.select_values += fields
self
end
@@ -254,6 +275,10 @@ module ActiveRecord
#
# User.group('name AS grouped_name, age')
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
+ #
+ # Passing in an array of attributes to group by is also supported.
+ # User.select([:id, :first_name]).group(:id, :first_name).first(3)
+ # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
def group(*args)
check_if_method_has_arguments!(:group, args)
spawn.group!(*args)
@@ -268,15 +293,6 @@ module ActiveRecord
# Allows to specify an order attribute:
#
- # User.order('name')
- # => SELECT "users".* FROM "users" ORDER BY name
- #
- # User.order('name DESC')
- # => SELECT "users".* FROM "users" ORDER BY name DESC
- #
- # User.order('name DESC, email')
- # => SELECT "users".* FROM "users" ORDER BY name DESC, email
- #
# User.order(:name)
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
#
@@ -285,6 +301,15 @@ module ActiveRecord
#
# User.order(:name, email: :desc)
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
+ #
+ # User.order('name')
+ # => SELECT "users".* FROM "users" ORDER BY name
+ #
+ # User.order('name DESC')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
+ #
+ # User.order('name DESC, email')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
def order(*args)
check_if_method_has_arguments!(:order, args)
spawn.order!(*args)
@@ -812,22 +837,25 @@ module ActiveRecord
end
def reverse_order! # :nodoc:
- self.reverse_order_value = !reverse_order_value
+ orders = order_values.uniq
+ orders.reject!(&:blank?)
+ self.order_values = reverse_sql_order(orders)
self
end
# Returns the Arel object associated with the relation.
- def arel
+ def arel # :nodoc:
@arel ||= build_arel
end
- # Like #arel, but ignores the default scope of the model.
+ private
+
def build_arel
arel = Arel::SelectManager.new(table.engine, table)
build_joins(arel, joins_values.flatten) unless joins_values.empty?
- collapse_wheres(arel, (where_values - ['']).uniq)
+ collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
@@ -844,11 +872,18 @@ module ActiveRecord
arel.from(build_from) if from_value
arel.lock(lock_value) if lock_value
+ # Reorder bind indexes if joins produced bind values
+ if arel.bind_values.any?
+ bvs = arel.bind_values + bind_values
+ arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
+ column = bvs[i].first
+ bp.replace connection.substitute_at(column, i)
+ end
+ end
+
arel
end
- private
-
def symbol_unscoping(scope)
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
@@ -859,8 +894,9 @@ module ActiveRecord
case scope
when :order
- self.reverse_order_value = false
result = []
+ when :where
+ self.bind_values = []
else
result = [] unless single_val_method
end
@@ -876,8 +912,6 @@ module ActiveRecord
when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
subrelation.name == target_value
- else
- raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
end
end
@@ -913,18 +947,16 @@ module ActiveRecord
def build_where(opts, other = [])
case opts
when String, Array
- #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
- values = Hash === other.first ? other.first.values : other
-
- values.grep(ActiveRecord::Relation) do |rel|
- self.bind_values += rel.bind_values
- end
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
+ bv_len = bind_values.length
+ tmp_opts, bind_values = create_binds(opts, bv_len)
+ self.bind_values += bind_values
+
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
attributes.values.grep(ActiveRecord::Relation) do |rel|
self.bind_values += rel.bind_values
end
@@ -935,6 +967,29 @@ module ActiveRecord
end
end
+ def create_binds(opts, idx)
+ bindable, non_binds = opts.partition do |column, value|
+ case value
+ when String, Integer, ActiveRecord::StatementCache::Substitute
+ @klass.columns_hash.include? column.to_s
+ else
+ false
+ end
+ end
+
+ new_opts = {}
+ binds = []
+
+ bindable.each_with_index do |(column,value), index|
+ binds.push [@klass.columns_hash[column.to_s], value]
+ new_opts[column] = connection.substitute_at(column, index + idx)
+ end
+
+ non_binds.each { |column,value| new_opts[column] = value }
+
+ [new_opts, binds]
+ end
+
def build_from
opts, name = from_value
case opts
@@ -976,9 +1031,12 @@ module ActiveRecord
join_list
)
- joins = join_dependency.join_constraints stashed_association_joins
+ join_infos = join_dependency.join_constraints stashed_association_joins
- joins.each { |join| manager.from(join) }
+ join_infos.each do |info|
+ info.joins.each { |join| manager.from(join) }
+ manager.bind_values.concat info.binds
+ end
manager.join_sources.concat(join_list)
@@ -987,9 +1045,10 @@ module ActiveRecord
def build_select(arel, selects)
if !selects.empty?
- arel.project(*selects)
- elsif from_value
- arel.project(Arel.star)
+ expanded_select = selects.map do |field|
+ columns_hash.key?(field.to_s) ? arel_table[field] : field
+ end
+ arel.project(*expanded_select)
else
arel.project(@klass.arel_table[Arel.star])
end
@@ -1020,15 +1079,19 @@ module ActiveRecord
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
- orders = reverse_sql_order(orders) if reverse_order_value
arel.order(*orders) unless orders.empty?
end
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
+ 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
+
def validate_order_args(args)
- args.grep(Hash) do |h|
- unless (h.values - [:asc, :desc]).empty?
- raise ArgumentError, 'Direction should be :asc or :desc'
+ args.each do |arg|
+ next unless arg.is_a?(Hash)
+ arg.each do |_key, value|
+ raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
+ "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
end
end
end
@@ -1045,10 +1108,12 @@ module ActiveRecord
order_args.map! do |arg|
case arg
when Symbol
+ arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
table[arg].asc
when Hash
arg.map { |field, dir|
- table[field].send(dir)
+ field = klass.attribute_alias(field) if klass.attribute_alias?(field)
+ table[field].send(dir.downcase)
}
else
arg
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 2552cbd234..57d66bce4b 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -58,6 +58,9 @@ module ActiveRecord
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
def only(*onlies)
+ if onlies.any? { |o| o == :where }
+ onlies << :bind
+ end
relation_with values.slice(*onlies)
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 469451e2f4..228b2aa60f 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -54,7 +54,7 @@ module ActiveRecord
if block_given?
hash_rows.each { |row| yield row }
else
- hash_rows.to_enum
+ hash_rows.to_enum { @rows.size }
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index dacaec26b7..1aa93ffbb3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -29,6 +29,7 @@ module ActiveRecord
end
end
alias_method :sanitize_sql, :sanitize_sql_for_conditions
+ alias_method :sanitize_conditions, :sanitize_sql
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
@@ -91,7 +92,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
- connection.visitor.accept b
+ connection.visitor.compile b
}.join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -106,6 +107,13 @@ module ActiveRecord
end.join(', ')
end
+ # Sanitizes a +string+ so that it is safe to use within a sql
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
+ def sanitize_sql_like(string, escape_character = "\\")
+ pattern = Regexp.union(escape_character, "%", "_")
+ string.gsub(pattern) { |x| [escape_character, x].join }
+ end
+
# Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
@@ -122,8 +130,6 @@ module ActiveRecord
end
end
- alias_method :sanitize_conditions, :sanitize_sql
-
def replace_bind_variables(statement, values) #:nodoc:
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 0cf3d59985..3e43591672 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -27,6 +27,11 @@ module ActiveRecord
end
end
+ def initialize_internals_callback
+ super
+ populate_with_current_scope_attributes
+ end
+
# This class stores the +:current_scope+ and +:ignore_default_scope+ values
# for different classes. The registry is stored as a thread local, which is
# accessed through +ScopeRegistry.current+.
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 01fec31544..18190cb535 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
module ClassMethods
- # Returns a scope for the model without the +default_scope+.
+ # Returns a scope for the model without the previously set scopes.
#
# class Post < ActiveRecord::Base
# def self.default_scope
@@ -19,11 +19,12 @@ module ActiveRecord
# end
# end
#
- # Post.all # Fires "SELECT * FROM posts WHERE published = true"
- # Post.unscoped.all # Fires "SELECT * FROM posts"
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
#
# This method also accepts a block. All queries inside the block will
- # not use the +default_scope+:
+ # not use the previously set scopes.
#
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
@@ -93,14 +94,14 @@ module ActiveRecord
self.default_scopes += [scope]
end
- def build_default_scope # :nodoc:
+ def build_default_scope(base_rel = relation) # :nodoc:
if !Base.is_a?(method(:default_scope).owner)
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
elsif default_scopes.any?
evaluate_default_scope do
- default_scopes.inject(relation) do |default_scope, scope|
- default_scope.merge(unscoped { scope.call })
+ default_scopes.inject(base_rel) do |default_scope, scope|
+ default_scope.merge(base_rel.scoping { scope.call })
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 2a5718f388..49cadb66d0 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -139,6 +139,12 @@ module ActiveRecord
# Article.published.featured.latest_article
# Article.featured.titles
def scope(name, body, &block)
+ if dangerous_class_method?(name)
+ raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
+ "on the model \"#{self.name}\", but Active Record already defined " \
+ "a class method with the same name."
+ end
+
extension = Module.new(&block) if block
singleton_class.send(:define_method, name) do |*args|
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index dd4ee0c4a0..aece446384 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -14,13 +14,87 @@ module ActiveRecord
# 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
- def initialize
- @relation = yield
- raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
+ class Substitute; end
+
+ class Query
+ def initialize(sql)
+ @sql = sql
+ end
+
+ def sql_for(binds, connection)
+ @sql
+ end
+ end
+
+ class PartialQuery < Query
+ def initialize values
+ @values = values
+ @indexes = values.each_with_index.find_all { |thing,i|
+ Arel::Nodes::BindParam === thing
+ }.map(&:last)
+ end
+
+ def sql_for(binds, connection)
+ val = @values.dup
+ binds = binds.dup
+ @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
+ val.join
+ end
+ end
+
+ def self.query(visitor, ast)
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
+ end
+
+ def self.partial_query(visitor, ast, collector)
+ collected = visitor.accept(ast, collector).value
+ PartialQuery.new collected
+ end
+
+ class Params
+ def bind; Substitute.new; end
end
- def execute
- @relation.dup.to_a
+ class BindMap
+ def initialize(bind_values)
+ @indexes = []
+ @bind_values = bind_values
+
+ bind_values.each_with_index do |(_, value), i|
+ if Substitute === value
+ @indexes << i
+ end
+ end
+ end
+
+ def bind(values)
+ bvs = @bind_values.map { |pair| pair.dup }
+ @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
+ bvs
+ end
+ end
+
+ attr_reader :bind_map, :query_builder
+
+ def self.create(connection, block = Proc.new)
+ relation = block.call Params.new
+ bind_map = BindMap.new relation.bind_values
+ query_builder = connection.cacheable_query relation.arel
+ new query_builder, bind_map
+ end
+
+ def initialize(query_builder, bind_map)
+ @query_builder = query_builder
+ @bind_map = bind_map
+ end
+
+ def execute(params, klass, connection)
+ bind_values = bind_map.bind params
+
+ sql = query_builder.sql_for bind_values, connection
+
+ klass.find_by_sql sql, bind_values
end
+ alias :call :execute
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 6ce0495f6f..168b338b97 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# Example usage of +DatabaseTasks+ outside Rails could look as such:
#
# include ActiveRecord::Tasks
- # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
# DatabaseTasks.db_dir = 'db'
# # other settings...
#
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index e0541b7681..6c30ccab72 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -37,13 +37,13 @@ module ActiveRecord
end
def initialize_dup(other) # :nodoc:
- clear_timestamp_attributes
super
+ clear_timestamp_attributes
end
private
- def create_record
+ def _create_record
if self.record_timestamps
current_time = current_time_from_proper_timezone
@@ -57,7 +57,7 @@ module ActiveRecord
super
end
- def update_record(*args)
+ def _update_record(*args)
if should_record_timestamps?
current_time = current_time_from_proper_timezone
@@ -71,7 +71,7 @@ module ActiveRecord
end
def should_record_timestamps?
- self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
+ self.record_timestamps && (!partial_writes? || changed?)
end
def timestamp_attributes_for_create_in_model
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 45313b5e75..17f76b63b3 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -6,9 +6,6 @@ module ActiveRecord
extend ActiveSupport::Concern
ACTIONS = [:create, :destroy, :update]
- class TransactionError < ActiveRecordError # :nodoc:
- end
-
included do
define_callbacks :commit, :rollback,
terminator: ->(_, result) { result == false },
@@ -243,15 +240,14 @@ module ActiveRecord
def set_options_for_callbacks!(args)
options = args.last
if options.is_a?(Hash) && options[:on]
- assert_valid_transaction_action(options[:on])
- options[:if] = Array(options[:if])
fire_on = Array(options[:on])
+ assert_valid_transaction_action(fire_on)
+ options[:if] = Array(options[:if])
options[:if] << "transaction_include_any_action?(#{fire_on})"
end
end
def assert_valid_transaction_action(actions)
- actions = Array(actions)
if (actions - ACTIONS).any?
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
end
@@ -277,6 +273,10 @@ module ActiveRecord
with_transaction_returning_status { super }
end
+ def touch(*) #:nodoc:
+ with_transaction_returning_status { super }
+ end
+
# Reset id and @new_record if the transaction rolls back.
def rollback_active_record_state!
remember_transaction_record_state
@@ -295,7 +295,7 @@ module ActiveRecord
def committed! #:nodoc:
run_callbacks :commit if destroyed? || persisted?
ensure
- clear_transaction_record_state
+ @_start_transaction_state.clear
end
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
@@ -369,7 +369,7 @@ module ActiveRecord
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state.has_key?(:id)
- self.id = restore_state[:id]
+ write_attribute(self.class.primary_key, restore_state[:id])
else
@attributes.delete(self.class.primary_key)
@attributes_cache.delete(self.class.primary_key)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 26dca415ff..9999624fcf 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -60,6 +60,8 @@ module ActiveRecord
# Runs all the validations within the specified context. Returns +true+ if
# no errors are found, +false+ otherwise.
#
+ # Aliased as validate.
+ #
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
#
@@ -71,6 +73,8 @@ module ActiveRecord
errors.empty? && output
end
+ alias_method :validate, :valid?
+
protected
def perform_validations(options={}) # :nodoc:
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 7ebe9dfec0..ee080451a9 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -13,6 +13,7 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
+ value = map_enum_attribute(finder_class, attribute, value)
value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
@@ -67,8 +68,7 @@ module ActiveRecord
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- value = klass.connection.case_sensitive_modifier(value) unless value.nil?
- table[attribute].eq(value)
+ klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
end
@@ -91,6 +91,12 @@ module ActiveRecord
value = coder.dump value if value && coder
value
end
+
+ def map_enum_attribute(klass, attribute, value)
+ mapping = klass.defined_enums[attribute.to_s]
+ value = mapping[value] if value && mapping
+ value
+ end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 863c3ebe4d..cf76a13b44 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveRecord
- # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
- STRING = ActiveRecord.version.to_s
+ gem_version
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 3968acba64..d3c853cfea 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -23,16 +23,16 @@ module ActiveRecord
case file_name
when /^(add|remove)_.*_(?:to|from)_(.*)/
@migration_action = $1
- @table_name = $2.pluralize
+ @table_name = normalize_table_name($2)
when /join_table/
if attributes.length == 2
@migration_action = 'join'
- @join_tables = attributes.map(&:plural_name)
+ @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name)
set_index_names
end
when /^create_(.+)/
- @table_name = $1.pluralize
+ @table_name = normalize_table_name($1)
@migration_template = "create_table_migration.rb"
end
end
@@ -61,6 +61,10 @@ module ActiveRecord
raise IllegalMigrationNameError.new(file_name)
end
end
+
+ def normalize_table_name(_table_name)
+ pluralize_table_names? ? _table_name.pluralize : _table_name.singularize
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index b67e70ec7e..90953ce6cd 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,5 +1,7 @@
require "cases/helper"
require "models/book"
+require "models/post"
+require "models/author"
module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
@@ -142,9 +144,9 @@ module ActiveRecord
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
end
end
-
- def test_foreign_key_violations_are_translated_to_specific_exception
- unless @connection.adapter_name == 'SQLite'
+
+ unless current_adapter?(:SQLite3Adapter)
+ def test_foreign_key_violations_are_translated_to_specific_exception
assert_raises(ActiveRecord::InvalidForeignKey) do
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
@@ -155,6 +157,18 @@ module ActiveRecord
end
end
end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
+ klass_has_fk = Class.new(ActiveRecord::Base) do
+ self.table_name = 'fk_test_has_fk'
+ end
+
+ assert_raises(ActiveRecord::InvalidForeignKey) do
+ has_fk = klass_has_fk.new
+ has_fk.fk_id = 1231231231
+ has_fk.save(validate: false)
+ end
+ end
end
def test_disable_referential_integrity
@@ -179,6 +193,27 @@ module ActiveRecord
assert result.is_a?(ActiveRecord::Result)
end
+ def test_select_methods_passing_a_association_relation
+ author = Author.create!(name: 'john')
+ Post.create!(author: author, title: 'foo', body: 'bar')
+ query = author.posts.select(:title)
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query))
+ assert @connection.select_all(query).is_a?(ActiveRecord::Result)
+ assert_equal "foo", @connection.select_value(query)
+ assert_equal ["foo"], @connection.select_values(query)
+ end
+
+ def test_select_methods_passing_a_relation
+ Post.create!(title: 'foo', body: 'bar')
+ query = Post.where(title: 'foo').select(:title)
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query))
+ assert @connection.select_all(query).is_a?(ActiveRecord::Result)
+ assert_equal "foo", @connection.select_value(query)
+ assert_equal ["foo"], @connection.select_values(query)
+ end
+
test "type_to_sql returns a String for unmapped types" do
assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
end
@@ -195,7 +230,7 @@ module ActiveRecord
@connection = Klass.connection
end
- def teardown
+ teardown do
Klass.remove_connection
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 0878925a6c..7c0f11b033 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -1,23 +1,23 @@
require "cases/helper"
+require 'support/connection_helper'
class ActiveSchemaTest < ActiveRecord::TestCase
- def setup
- @connection = ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ include ConnectionHelper
+ def setup
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
def execute(sql, name = nil) return sql end
end
end
- def teardown
- ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ teardown do
+ reset_connection
end
def test_add_index
- # add_index calls index_name_exists? which can't work since execute is stubbed
+ # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
+ def test_indexes_in_create
+ ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false)
+ ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
+
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
+ actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
+ t.index :zip
+ end
+
+ assert_equal expected, actual
+ end
+
private
def with_real_execute
ActiveRecord::Base.connection.singleton_class.class_eval do
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
index 97adb6b297..340fc95503 100644
--- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
@@ -3,10 +3,10 @@ require 'models/person'
class MysqlCaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
- validates_uniqueness_of :string_cs_column, :case_sensitive => false
- validates_uniqueness_of :string_ci_column, :case_sensitive => false
end
+ repair_validations(CollationTest)
+
def test_columns_include_collation_different_from_table
assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
@@ -18,6 +18,7 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
CollationTest.create!(:string_ci_column => 'A')
invalid = CollationTest.new(:string_ci_column => 'a')
queries = assert_sql { invalid.save }
@@ -26,10 +27,29 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
CollationTest.create!(:string_cs_column => 'A')
invalid = CollationTest.new(:string_cs_column => 'a')
queries = assert_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_match(/lower/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'A')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_match(/binary/i, ci_uniqueness_query)
+ end
+
+ def test_case_sensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'A')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
+ assert_no_match(/binary/i, cs_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 5cd5d8ac5f..412efa22ff 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -1,6 +1,11 @@
require "cases/helper"
+require 'support/connection_helper'
+require 'support/ddl_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include DdlHelper
+
class Klass < ActiveRecord::Base
end
@@ -69,59 +74,50 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_exec_no_binds
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
+ with_example_table do
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ # if there are no bind parameters, it will return a string (due to
+ # the libmysql api)
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_exec_with_binds
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ with_example_table do
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
def test_exec_typecasts_bind_vals
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ with_example_table do
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
# Test that MySQL allows multiple results for stored procedures
@@ -166,12 +162,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase
private
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
- end
+ def with_example_table(&block)
+ definition ||= <<-SQL
+ `id` int(11) auto_increment PRIMARY KEY,
+ `data` varchar(255)
+ SQL
+ super(@connection, 'ex', definition, &block)
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 578f6301bd..1699380eb3 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -1,19 +1,15 @@
# encoding: utf-8
require "cases/helper"
+require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+
def setup
@conn = ActiveRecord::Base.connection
- @conn.exec_query('drop table if exists ex')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex` (
- `id` int(11) auto_increment PRIMARY KEY,
- `number` integer,
- `data` varchar(255))
- eosql
end
def test_bad_connection_mysql
@@ -25,8 +21,10 @@ module ActiveRecord
end
def test_valid_column
- column = @conn.columns('ex').find { |col| col.name == 'id' }
- assert @conn.valid_type?(column.type)
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
end
def test_invalid_column
@@ -38,31 +36,35 @@ module ActiveRecord
end
def test_exec_insert_number
- insert(@conn, 'number' => 10)
+ with_example_table do
+ insert(@conn, 'number' => 10)
- result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
+ result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
- assert_equal 1, result.rows.length
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- assert_equal '10', result.rows.last.last
+ assert_equal 1, result.rows.length
+ # if there are no bind parameters, it will return a string (due to
+ # the libmysql api)
+ assert_equal '10', result.rows.last.last
+ end
end
def test_exec_insert_string
- str = 'いただきます!'
- insert(@conn, 'number' => 10, 'data' => str)
+ with_example_table do
+ str = 'いただきます!'
+ insert(@conn, 'number' => 10, 'data' => str)
- result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
+ result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
- value = result.rows.last.last
+ value = result.rows.last.last
- # FIXME: this should probably be inside the mysql AR adapter?
- value.force_encoding(@conn.client_encoding)
+ # FIXME: this should probably be inside the mysql AR adapter?
+ value.force_encoding(@conn.client_encoding)
- # The strings in this file are utf-8, so transcode to utf-8
- value.encode!(Encoding::UTF_8)
+ # The strings in this file are utf-8, so transcode to utf-8
+ value.encode!(Encoding::UTF_8)
- assert_equal str, value
+ assert_equal str, value
+ end
end
def test_tables_quoting
@@ -74,46 +76,37 @@ module ActiveRecord
end
def test_pk_and_sequence_for
- pk, seq = @conn.pk_and_sequence_for('ex')
- assert_equal 'id', pk
- assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ with_example_table do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
end
def test_pk_and_sequence_for_with_non_standard_primary_key
- @conn.exec_query('drop table if exists ex_with_non_standard_pk')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_non_standard_pk` (
- `code` INT(11) auto_increment,
- PRIMARY KEY (`code`))
- eosql
- pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk')
- assert_equal 'code', pk
- assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq
+ with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'code', pk
+ assert_equal @conn.default_sequence_name('ex', 'code'), seq
+ end
end
def test_pk_and_sequence_for_with_custom_index_type_pk
- @conn.exec_query('drop table if exists ex_with_custom_index_type_pk')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_custom_index_type_pk` (
- `id` INT(11) auto_increment,
- PRIMARY KEY USING BTREE (`id`))
- eosql
- pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk')
- assert_equal 'id', pk
- assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
+ with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
end
def test_tinyint_integer_typecasting
- @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_non_boolean_tinyint_column` (
- `status` TINYINT(4))
- eosql
- insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column')
+ with_example_table '`status` TINYINT(4)' do
+ insert(@conn, { 'status' => 2 }, 'ex')
- result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column')
+ result = @conn.exec_query('SELECT status FROM ex')
- assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ end
end
def test_supports_extensions
@@ -140,6 +133,15 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+
+ def with_example_table(definition = nil, &block)
+ definition ||= <<-SQL
+ `id` int(11) auto_increment PRIMARY KEY,
+ `number` integer,
+ `data` varchar(255)
+ SQL
+ super(@conn, 'ex', definition, &block)
+ end
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 8eb9565963..61ae0abfd1 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'distinct_select'=>'distinct_id int, select_id int'
end
- def teardown
+ teardown do
drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 4ccf568406..cefc3e3c7e 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -1,23 +1,23 @@
require "cases/helper"
+require 'support/connection_helper'
class ActiveSchemaTest < ActiveRecord::TestCase
- def setup
- @connection = ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ include ConnectionHelper
+ def setup
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
def execute(sql, name = nil) return sql end
end
end
- def teardown
- ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ teardown do
+ reset_connection
end
def test_add_index
- # add_index calls index_name_exists? which can't work since execute is stubbed
+ # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
+ def test_indexes_in_create
+ ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false)
+ ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
+
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
+ actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
+ t.index :zip
+ end
+
+ assert_equal expected, actual
+ end
+
private
def with_real_execute
ActiveRecord::Base.connection.singleton_class.class_eval do
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 6bcc113482..09bebf3071 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -3,10 +3,10 @@ require 'models/person'
class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
- validates_uniqueness_of :string_cs_column, :case_sensitive => false
- validates_uniqueness_of :string_ci_column, :case_sensitive => false
end
+ repair_validations(CollationTest)
+
def test_columns_include_collation_different_from_table
assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
@@ -18,6 +18,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
CollationTest.create!(:string_ci_column => 'A')
invalid = CollationTest.new(:string_ci_column => 'a')
queries = assert_sql { invalid.save }
@@ -26,10 +27,29 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
CollationTest.create!(:string_cs_column => 'A')
invalid = CollationTest.new(:string_cs_column => 'a')
queries = assert_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)}
assert_match(/lower/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'A')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_match(/binary/i, ci_uniqueness_query)
+ end
+
+ def test_case_sensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'A')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
+ assert_no_match(/binary/i, cs_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 9b7202c915..182d9409c7 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -1,15 +1,18 @@
require "cases/helper"
+require 'support/connection_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
def setup
super
@subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
end
def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
+ ActiveSupport::Notifications.unsubscribe(@subscription)
super
end
@@ -97,14 +100,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase
@connection.execute "DROP TABLE `bar_baz`"
end
- private
-
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
+ if mysql_56?
+ def test_quote_time_usec
+ assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0))
+ assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 1cd356e868..675703caa1 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -9,15 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %r(developers |.* const), explain
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(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` IN (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 1a82308176..799d927ee4 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'distinct_select'=>'distinct_id int, select_id int'
end
- def teardown
+ teardown do
drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 22dd48e113..3808db5141 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -7,7 +7,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
remove_method :execute
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index d71e2aa2bb..18dd4a6de8 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -19,14 +19,17 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
@column = PgArray.columns.find { |c| c.name == 'tags' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists pg_arrays'
end
def test_column
assert_equal :string, @column.type
+ assert_equal "character varying", @column.sql_type
assert @column.array
assert_not @column.text?
+ assert_not @column.number?
+ assert_not @column.binary?
ratings_column = PgArray.columns_hash['ratings']
assert_equal :integer, ratings_column.type
@@ -34,6 +37,28 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_not ratings_column.number?
end
+ def test_default
+ @connection.add_column 'pg_arrays', 'score', :integer, array: true, default: [4, 4, 2]
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["score"]
+
+ assert_equal([4, 4, 2], column.default)
+ assert_equal([4, 4, 2], PgArray.new.score)
+ ensure
+ PgArray.reset_column_information
+ end
+
+ def test_default_strings
+ @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"]
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["names"]
+
+ assert_equal(["foo", "bar"], column.default)
+ assert_equal(["foo", "bar"], PgArray.new.names)
+ ensure
+ PgArray.reset_column_information
+ end
+
def test_change_column_with_array
@connection.add_column :pg_arrays, :snippets, :string, array: true, default: []
@connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}"
@@ -93,6 +118,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]])
end
+ def test_with_empty_strings
+ assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ])
+ end
+
+ def test_with_multi_dimensional_empty_strings
+ assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]])
+ end
+
+ def test_with_arbitrary_whitespace
+ assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]])
+ end
+
def test_multi_dimensional_with_integers
assert_cycle(:ratings, [[[1], [7]], [[8], [10]]])
end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index b8dd35c4c5..e3478856c8 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -23,7 +23,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists bytea_data_type'
end
@@ -70,6 +70,23 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
assert_equal(data, record.payload)
end
+ def test_via_to_sql
+ data = "'\u001F\\"
+ ByteaDataType.create(payload: data)
+ sql = ByteaDataType.where(payload: data).select(:payload).to_sql
+ result = @connection.query(sql)
+ assert_equal([[data]], result)
+ end
+
+ def test_via_to_sql_with_complicating_connection
+ Thread.new do
+ other_conn = ActiveRecord::Base.connection
+ other_conn.execute('SET standard_conforming_strings = off')
+ end.join
+
+ test_via_to_sql
+ end
+
def test_write_binary
data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log'))
assert(data.size > 1)
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
new file mode 100644
index 0000000000..948bf49a54
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+
+require 'cases/helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+if ActiveRecord::Base.connection.supports_extensions?
+ class PostgresqlCitextTest < ActiveRecord::TestCase
+ class Citext < ActiveRecord::Base
+ self.table_name = 'citexts'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ unless @connection.extension_enabled?('citext')
+ @connection.enable_extension 'citext'
+ @connection.commit_db_transaction
+ end
+
+ @connection.reconnect!
+
+ @connection.create_table('citexts') do |t|
+ t.citext 'cival'
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS citexts;'
+ @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;'
+ end
+
+ def test_citext_enabled
+ assert @connection.extension_enabled?('citext')
+ end
+
+ def test_column
+ column = Citext.columns_hash['cival']
+ assert_equal :citext, column.type
+ assert_equal 'citext', column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_change_table_supports_json
+ @connection.transaction do
+ @connection.change_table('citexts') do |t|
+ t.citext 'username'
+ end
+ Citext.reset_column_information
+ column = Citext.columns.find { |c| c.name == 'username' }
+ assert_equal :citext, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
+ end
+ ensure
+ Citext.reset_column_information
+ end
+
+ def test_write
+ x = Citext.new(cival: 'Some CI Text')
+ x.save!
+ citext = Citext.first
+ assert_equal "Some CI Text", citext.cival
+
+ citext.cival = "Some NEW CI Text"
+ citext.save!
+
+ assert_equal "Some NEW CI Text", citext.reload.cival
+ end
+
+ def test_select_case_insensitive
+ @connection.execute "insert into citexts (cival) values('Cased Text')"
+ x = Citext.where(cival: 'cased text').first
+ assert_equal 'Cased Text', x.cival
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
new file mode 100644
index 0000000000..224b1b770b
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlCompositeTest < ActiveRecord::TestCase
+ class PostgresqlComposite < ActiveRecord::Base
+ self.table_name = "postgresql_composites"
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
+ @connection.execute 'DROP TYPE IF EXISTS full_address'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute <<-SQL
+ CREATE TYPE full_address AS
+ (
+ city VARCHAR(90),
+ street VARCHAR(90)
+ );
+ SQL
+ @connection.create_table('postgresql_composites') do |t|
+ t.column :address, :full_address
+ end
+ end
+ end
+
+ def test_column
+ column = PostgresqlComposite.columns_hash["address"]
+ # TODO: Composite columns should have a type
+ assert_nil column.type
+ assert_equal "full_address", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_composite_mapping
+ @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
+ composite = PostgresqlComposite.first
+ assert_equal "(Paris,Champs-Élysées)", composite.address
+
+ composite.address = "(Paris,Rue Basse)"
+ composite.save!
+
+ assert_equal '(Paris,"Rue Basse")', composite.reload.address
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 90cca7d3e6..5f84c893c0 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -1,19 +1,22 @@
require "cases/helper"
+require 'support/connection_helper'
module ActiveRecord
class PostgresqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
class NonExistentTable < ActiveRecord::Base
end
def setup
super
@subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
end
def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
+ ActiveSupport::Notifications.unsubscribe(@subscription)
super
end
@@ -45,6 +48,37 @@ module ActiveRecord
assert_equal 'off', expect
end
+ def test_reset
+ @connection.query('ROLLBACK')
+ @connection.query('SET geqo TO off')
+
+ # Verify the setting has been applied.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'off', expect
+
+ @connection.reset!
+
+ # Verify the setting has been cleared.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'on', expect
+ end
+
+ def test_reset_with_transaction
+ @connection.query('ROLLBACK')
+ @connection.query('SET geqo TO off')
+
+ # Verify the setting has been applied.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'off', expect
+
+ @connection.query('BEGIN')
+ @connection.reset!
+
+ # Verify the setting has been cleared.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'on', expect
+ end
+
def test_tables_logs_name
@connection.tables('hello')
assert_equal 'SCHEMA', @subscriber.logged[0][1]
@@ -91,40 +125,50 @@ module ActiveRecord
assert_operator plan.length, :>, 0
end
- # Must have with_manual_interventions set to true for this
- # test to run.
+ # Must have PostgreSQL >= 9.2, or with_manual_interventions set to
+ # true for this test to run.
+ #
# When prompted, restart the PostgreSQL server with the
# "-m fast" option or kill the individual connection assuming
# you know the incantation to do that.
# To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ...
# sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast"
- if ARTest.config['with_manual_interventions']
- def test_reconnection_after_actual_disconnection_with_verify
- original_connection_pid = @connection.query('select pg_backend_pid()')
+ def test_reconnection_after_actual_disconnection_with_verify
+ original_connection_pid = @connection.query('select pg_backend_pid()')
- # Sanity check.
- assert @connection.active?
+ # Sanity check.
+ assert @connection.active?
+ if @connection.send(:postgresql_version) >= 90200
+ secondary_connection = ActiveRecord::Base.connection_pool.checkout
+ secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
+ ActiveRecord::Base.connection_pool.checkin(secondary_connection)
+ elsif ARTest.config['with_manual_interventions']
puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' +
'server with the "-m fast" option) and then press enter.'
$stdin.gets
+ else
+ # We're not capable of terminating the backend ourselves, and
+ # we're not allowed to seek assistance; bail out without
+ # actually testing anything.
+ return
+ end
- @connection.verify!
+ @connection.verify!
- assert @connection.active?
+ assert @connection.active?
- # If we get no exception here, then either we re-connected successfully, or
- # we never actually got disconnected.
- new_connection_pid = @connection.query('select pg_backend_pid()')
+ # If we get no exception here, then either we re-connected successfully, or
+ # we never actually got disconnected.
+ new_connection_pid = @connection.query('select pg_backend_pid()')
- assert_not_equal original_connection_pid, new_connection_pid,
- "umm -- looks like you didn't break the connection, because we're still " +
- "successfully querying with the same connection pid."
+ assert_not_equal original_connection_pid, new_connection_pid,
+ "umm -- looks like you didn't break the connection, because we're still " +
+ "successfully querying with the same connection pid."
- # Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ # Repair all fixture connections so other tests won't break.
+ @fixture_connections.each do |c|
+ c.verify!
end
end
@@ -157,17 +201,5 @@ module ActiveRecord
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
end
end
-
- private
-
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
- end
- end
-
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 04a458fbce..e7dda1a1af 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -27,9 +27,6 @@ end
class PostgresqlTimestampWithZone < ActiveRecord::Base
end
-class PostgresqlUUID < ActiveRecord::Base
-end
-
class PostgresqlLtree < ActiveRecord::Base
end
@@ -68,14 +65,19 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@first_oid = PostgresqlOid.find(1)
@connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')")
-
- @connection.execute("INSERT INTO postgresql_uuids (id, guid, compact_guid) VALUES(1, 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')")
- @first_uuid = PostgresqlUUID.find(1)
end
- def teardown
+ teardown do
[PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress,
- PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all)
+ PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all)
+ end
+
+ def test_array_escaping
+ unknown = %(foo\\",bar,baz,\\)
+ nicknames = ["hello_#{unknown}"]
+ ar = PostgresqlArray.create!(nicknames: nicknames, id: 100)
+ ar.reload
+ assert_equal nicknames, ar.nicknames
end
def test_data_type_of_array_types
@@ -116,10 +118,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
- def test_data_type_of_uuid_types
- assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type
- end
-
def test_array_values
assert_equal [35000,21000,18000,17000], @first_array.commission_by_quarter
assert_equal ['foo','bar','baz'], @first_array.nicknames
@@ -172,11 +170,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
- def test_uuid_values
- assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid
- assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid
- end
-
def test_bit_string_values
assert_equal '00010101', @first_bit_string.bit_string
assert_equal '00010101', @first_bit_string.bit_string_varying
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
new file mode 100644
index 0000000000..5286a847a4
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlDomainTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ class PostgresqlDomain < ActiveRecord::Base
+ self.table_name = "postgresql_domains"
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)"
+ @connection.create_table('postgresql_domains') do |t|
+ t.column :price, :custom_money
+ end
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_domains'
+ @connection.execute 'DROP DOMAIN IF EXISTS custom_money'
+ reset_connection
+ end
+
+ def test_column
+ column = PostgresqlDomain.columns_hash["price"]
+ assert_equal :decimal, column.type
+ assert_equal "custom_money", column.sql_type
+ assert column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_domain_acts_like_basetype
+ PostgresqlDomain.create price: ""
+ record = PostgresqlDomain.first
+ assert_nil record.price
+
+ record.price = "34.15"
+ record.save!
+
+ assert_equal BigDecimal.new("34.15"), record.reload.price
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
new file mode 100644
index 0000000000..4146b117f6
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlEnumTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ class PostgresqlEnum < ActiveRecord::Base
+ self.table_name = "postgresql_enums"
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute <<-SQL
+ CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+ SQL
+ @connection.create_table('postgresql_enums') do |t|
+ t.column :current_mood, :mood
+ end
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_enums'
+ @connection.execute 'DROP TYPE IF EXISTS mood'
+ reset_connection
+ end
+
+ def test_column
+ column = PostgresqlEnum.columns_hash["current_mood"]
+ assert_equal :enum, column.type
+ assert_equal "mood", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_enum_mapping
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ enum = PostgresqlEnum.first
+ assert_equal "sad", enum.current_mood
+
+ enum.current_mood = "happy"
+ enum.save!
+
+ assert_equal "happy", enum.reload.current_mood
+ end
+
+ def test_invalid_enum_update
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ enum = PostgresqlEnum.first
+ enum.current_mood = "angry"
+
+ assert_raise ActiveRecord::StatementInvalid do
+ enum.save
+ end
+ end
+
+ def test_no_oid_warning
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ stderr_output = capture(:stderr) { PostgresqlEnum.first }
+
+ assert stderr_output.blank?
+ end
+
+ def test_enum_type_cast
+ enum = PostgresqlEnum.new
+ enum.current_mood = :happy
+
+ assert_equal "happy", enum.current_mood
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 0b61f61572..416f84cb38 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
assert_match %(QUERY PLAN), explain
assert_match %(Index Scan using developers_pkey on developers), explain
end
@@ -17,9 +17,9 @@ module ActiveRecord
def test_explain_with_eager_loading
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 "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
assert_match %(Index Scan using developers_pkey on developers), 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" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index d8782f5eaa..c24c4b0d56 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -24,13 +24,14 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
@connection.transaction do
@connection.create_table('hstores') do |t|
t.hstore 'tags', :default => ''
+ t.hstore 'payload', array: true
t.hstore 'settings'
end
end
@column = Hstore.columns.find { |c| c.name == 'tags' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists hstores'
end
@@ -53,6 +54,22 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_column
assert_equal :hstore, @column.type
+ assert_equal "hstore", @column.sql_type
+ assert_not @column.number?
+ assert_not @column.text?
+ assert_not @column.binary?
+ assert_not @column.array
+ end
+
+ def test_default
+ @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"'
+ Hstore.reset_column_information
+ column = Hstore.columns_hash["permissions"]
+
+ assert_equal({"users"=>"read", "articles"=>"write"}, column.default)
+ assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions)
+ ensure
+ Hstore.reset_column_information
end
def test_change_table_supports_hstore
@@ -182,6 +199,30 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_equal({'1' => '2'}, x.tags)
end
+ def test_array_cycle
+ assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}])
+ end
+
+ def test_array_strings_with_quotes
+ assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}])
+ end
+
+ def test_array_strings_with_commas
+ assert_array_cycle([{'this,has' => 'many,values'}])
+ end
+
+ def test_array_strings_with_array_delimiters
+ assert_array_cycle(['{' => '}'])
+ end
+
+ def test_array_strings_with_null_strings
+ assert_array_cycle([{'NULL' => 'NULL'}])
+ end
+
+ def test_contains_nils
+ assert_array_cycle([{'NULL' => nil}])
+ end
+
def test_select_multikey
@connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
x = Hstore.first
@@ -237,6 +278,20 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
private
+ def assert_array_cycle(array)
+ # test creation
+ x = Hstore.create!(payload: array)
+ x.reload
+ assert_equal(array, x.payload)
+
+ # test updating
+ x = Hstore.create!(payload: [])
+ x.payload = array
+ x.save!
+ x.reload
+ assert_equal(array, x.payload)
+ end
+
def assert_cycle(hash)
# test creation
x = Hstore.create!(:tags => hash)
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 01e7334aad..ee793ffff2 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -21,17 +21,34 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
end
end
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without json"
+ skip "do not test on PG without json"
end
@column = JsonDataType.columns.find { |c| c.name == 'payload' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists json_data_type'
end
def test_column
- assert_equal :json, @column.type
+ column = JsonDataType.columns_hash["payload"]
+ assert_equal :json, column.type
+ assert_equal "json", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ @connection.add_column 'json_data_type', 'permissions', :json, default: '{"users": "read", "posts": ["read", "write"]}'
+ JsonDataType.reset_column_information
+ column = JsonDataType.columns_hash["permissions"]
+
+ assert_equal({"users"=>"read", "posts"=>["read", "write"]}, column.default)
+ assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions)
+ ensure
+ JsonDataType.reset_column_information
end
def test_change_table_supports_json
@@ -57,16 +74,16 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
end
def test_type_cast_json
- assert @column
+ column = JsonDataType.columns_hash["payload"]
data = "{\"a_key\":\"a_value\"}"
- hash = @column.class.string_to_json data
+ hash = column.class.string_to_json data
assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, @column.type_cast(data))
+ assert_equal({'a_key' => 'a_value'}, column.type_cast(data))
- assert_equal({}, @column.type_cast("{}"))
- assert_equal({'key'=>nil}, @column.type_cast('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
+ assert_equal({}, column.type_cast("{}"))
+ assert_equal({'key'=>nil}, column.type_cast('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
end
def test_rewrite
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 5d12ca75ca..718f37a380 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -10,6 +10,11 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+
+ unless @connection.extension_enabled?('ltree')
+ @connection.enable_extension 'ltree'
+ end
+
@connection.transaction do
@connection.create_table('ltrees') do |t|
t.ltree 'path'
@@ -19,13 +24,18 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
skip "do not test on PG without ltree"
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists ltrees'
end
def test_column
column = Ltree.columns_hash['path']
assert_equal :ltree, column.type
+ assert_equal "ltree", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
end
def test_write
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 131080913c..b7791078db 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,26 +1,31 @@
# encoding: utf-8
require "cases/helper"
+require 'support/ddl_helper'
+require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+ include ConnectionHelper
+
def setup
@connection = ActiveRecord::Base.connection
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
def test_bad_connection
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db')
connection = ActiveRecord::Base.postgresql_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.exec_query('SELECT 1')
end
end
def test_valid_column
- column = @connection.columns('ex').find { |col| col.name == 'id' }
- assert @connection.valid_type?(column.type)
+ with_example_table do
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ assert @connection.valid_type?(column.type)
+ end
end
def test_invalid_column
@@ -28,7 +33,9 @@ module ActiveRecord
end
def test_primary_key
- assert_equal 'id', @connection.primary_key('ex')
+ with_example_table do
+ assert_equal 'id', @connection.primary_key('ex')
+ end
end
def test_primary_key_works_tables_containing_capital_letters
@@ -36,15 +43,15 @@ module ActiveRecord
end
def test_non_standard_primary_key
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(data character varying(255) primary key)')
- assert_equal 'data', @connection.primary_key('ex')
+ with_example_table 'data character varying(255) primary key' do
+ assert_equal 'data', @connection.primary_key('ex')
+ end
end
def test_primary_key_returns_nil_for_no_pk
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer)')
- assert_nil @connection.primary_key('ex')
+ with_example_table 'id integer' do
+ assert_nil @connection.primary_key('ex')
+ end
end
def test_primary_key_raises_error_if_table_not_found
@@ -54,32 +61,40 @@ module ActiveRecord
end
def test_insert_sql_with_proprietary_returning_clause
- id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
- assert_equal "5150", id
+ with_example_table do
+ id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
+ assert_equal "5150", id
+ end
end
def test_insert_sql_with_quoted_schema_and_table_name
- id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_insert_sql_with_no_space_after_table_name
- id = @connection.insert_sql("insert into ex(number) values(5150)")
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql("insert into ex(number) values(5150)")
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_multiline_insert_sql
- id = @connection.insert_sql(<<-SQL)
- insert into ex(
- number)
- values(
- 5152
- )
- SQL
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
+ with_example_table do
+ id = @connection.insert_sql(<<-SQL)
+ insert into ex(
+ number)
+ values(
+ 5152
+ )
+ SQL
+ expect = @connection.query('select max(id) from ex').first.first
+ assert_equal expect, id
+ end
end
def test_insert_sql_with_returning_disabled
@@ -135,53 +150,104 @@ module ActiveRecord
end
def test_pk_and_sequence_for
- pk, seq = @connection.pk_and_sequence_for('ex')
- assert_equal 'id', pk
- assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ with_example_table do
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ end
end
def test_pk_and_sequence_for_with_non_standard_primary_key
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(code serial primary key)')
- pk, seq = @connection.pk_and_sequence_for('ex')
- assert_equal 'code', pk
- assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ with_example_table 'code serial primary key' do
+ pk, seq = @connection.pk_and_sequence_for('ex')
+ assert_equal 'code', pk
+ assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ end
end
def test_pk_and_sequence_for_returns_nil_if_no_seq
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer primary key)')
- assert_nil @connection.pk_and_sequence_for('ex')
+ with_example_table 'id integer primary key' do
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
end
def test_pk_and_sequence_for_returns_nil_if_no_pk
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id integer)')
- assert_nil @connection.pk_and_sequence_for('ex')
+ with_example_table 'id integer' do
+ assert_nil @connection.pk_and_sequence_for('ex')
+ end
end
def test_pk_and_sequence_for_returns_nil_if_table_not_found
assert_nil @connection.pk_and_sequence_for('unobtainium')
end
+ def test_pk_and_sequence_for_with_collision_pg_class_oid
+ @connection.exec_query('create table ex(id serial primary key)')
+ @connection.exec_query('create table ex2(id serial primary key)')
+
+ correct_depend_record = [
+ "'pg_class'::regclass",
+ "'ex_id_seq'::regclass",
+ '0',
+ "'pg_class'::regclass",
+ "'ex'::regclass",
+ '1',
+ "'a'"
+ ]
+
+ collision_depend_record = [
+ "'pg_attrdef'::regclass",
+ "'ex2_id_seq'::regclass",
+ '0',
+ "'pg_class'::regclass",
+ "'ex'::regclass",
+ '1',
+ "'a'"
+ ]
+
+ @connection.exec_query(
+ "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
+ )
+ @connection.exec_query(
+ "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})"
+ )
+ @connection.exec_query(
+ "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})"
+ )
+
+ seq = @connection.pk_and_sequence_for('ex').last
+ assert_equal 'ex_id_seq', seq
+
+ @connection.exec_query(
+ "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
+ )
+ ensure
+ @connection.exec_query('DROP TABLE IF EXISTS ex')
+ @connection.exec_query('DROP TABLE IF EXISTS ex2')
+ end
+
def test_exec_insert_number
- insert(@connection, 'number' => 10)
+ with_example_table do
+ insert(@connection, 'number' => 10)
- result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
+ result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
- assert_equal 1, result.rows.length
- assert_equal "10", result.rows.last.last
+ assert_equal 1, result.rows.length
+ assert_equal "10", result.rows.last.last
+ end
end
def test_exec_insert_string
- str = 'いただきます!'
- insert(@connection, 'number' => 10, 'data' => str)
+ with_example_table do
+ str = 'いただきます!'
+ insert(@connection, 'number' => 10, 'data' => str)
- result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
+ result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
- value = result.rows.last.last
+ value = result.rows.last.last
- assert_equal str, value
+ assert_equal str, value
+ end
end
def test_table_alias_length
@@ -191,44 +257,50 @@ module ActiveRecord
end
def test_exec_no_binds
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
-
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [['1', 'foo']], result.rows
+ with_example_table do
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
+
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_exec_with_binds
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_exec_typecasts_bind_vals
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [['1', 'foo']], result.rows
+ end
end
def test_substitute_at
@@ -240,9 +312,11 @@ module ActiveRecord
end
def test_partial_index
- @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
- index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
- assert_equal "(number > 100)", index.where
+ with_example_table do
+ @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
+ index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
+ assert_equal "(number > 100)", index.where
+ end
end
def test_columns_for_distinct_zero_orders
@@ -285,6 +359,43 @@ module ActiveRecord
end
end
+ def test_reload_type_map_for_newly_defined_types
+ @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
+ result = @connection.select_all "SELECT 'good'::feeling"
+ assert_instance_of(PostgreSQLAdapter::OID::Enum,
+ result.column_types["feeling"])
+ ensure
+ @connection.execute "DROP TYPE IF EXISTS feeling"
+ reset_connection
+ end
+
+ def test_only_reload_type_map_once_for_every_unknown_type
+ silence_warnings do
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 1, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyarray"
+ end
+ end
+ ensure
+ reset_connection
+ end
+
+ def test_only_warn_on_first_encounter_of_unknown_oid
+ warning = capture(:stderr) {
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ }
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
+ ensure
+ reset_connection
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
@@ -300,6 +411,10 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+ def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
+ super(@connection, 'ex', definition, &block)
+ end
+
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 1122f8b9a1..51846e22d9 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,46 +10,46 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean')
assert_equal 't', @conn.type_cast(true, nil)
assert_equal 't', @conn.type_cast(true, c)
end
def test_type_cast_false
- c = Column.new(nil, 1, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, OID::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 = Column.new(nil, ip, 'cidr')
+ 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 = Column.new(nil, ip, 'inet')
+ c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet')
assert_equal ip, @conn.type_cast(ip, c)
end
def test_quote_float_nan
nan = 0.0/0
- c = Column.new(nil, 1, 'float')
+ c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
assert_equal "'NaN'", @conn.quote(nan, c)
end
def test_quote_float_infinity
infinity = 1.0/0
- c = Column.new(nil, 1, 'float')
+ 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 = Column.new(nil, nil, 'varchar')
+ c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar')
assert_equal "'666'", @conn.quote(fixnum, c)
- c = Column.new(nil, nil, 'text')
+ c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text')
assert_equal "'666'", @conn.quote(fixnum, c)
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index a56b8ac791..060b17d071 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/connection_helper'
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
@@ -8,14 +9,20 @@ if ActiveRecord::Base.connection.supports_ranges?
end
class PostgresqlRangeTest < ActiveRecord::TestCase
- def teardown
- @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
- end
+ self.use_transactional_fixtures = false
+ include ConnectionHelper
def setup
- @connection = ActiveRecord::Base.connection
+ @connection = PostgresqlRange.connection
begin
@connection.transaction do
+ @connection.execute <<_SQL
+ CREATE TYPE floatrange AS RANGE (
+ subtype = float8,
+ subtype_diff = float8mi
+ );
+_SQL
+
@connection.create_table('postgresql_ranges') do |t|
t.daterange :date_range
t.numrange :num_range
@@ -24,9 +31,12 @@ if ActiveRecord::Base.connection.supports_ranges?
t.int4range :int4_range
t.int8range :int8_range
end
+
+ @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange'
end
+ PostgresqlRange.reset_column_information
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without range"
+ skip "do not test on PG without range"
end
insert_range(id: 101,
@@ -35,23 +45,26 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']",
tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']",
int4_range: "[1, 10]",
- int8_range: "[10, 100]")
+ int8_range: "[10, 100]",
+ float_range: "[0.5, 0.7]")
insert_range(id: 102,
- date_range: "(''2012-01-02'', ''2012-01-04'')",
+ date_range: "[''2012-01-02'', ''2012-01-04'')",
num_range: "[0.1, 0.2)",
ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')",
tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')",
- int4_range: "(1, 10)",
- int8_range: "(10, 100)")
+ int4_range: "[1, 10)",
+ int8_range: "[10, 100)",
+ float_range: "[0.5, 0.7)")
insert_range(id: 103,
- date_range: "(''2012-01-02'',]",
+ date_range: "[''2012-01-02'',]",
num_range: "[0.1,]",
ts_range: "[''2010-01-01 14:30'',]",
tstz_range: "[''2010-01-01 14:30:00+05'',]",
- int4_range: "(1,]",
- int8_range: "(10,]")
+ int4_range: "[1,]",
+ int8_range: "[10,]",
+ float_range: "[0.5,]")
insert_range(id: 104,
date_range: "[,]",
@@ -59,15 +72,17 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range: "[,]",
tstz_range: "[,]",
int4_range: "[,]",
- int8_range: "[,]")
+ int8_range: "[,]",
+ float_range: "[,]")
insert_range(id: 105,
- date_range: "(''2012-01-02'', ''2012-01-02'')",
- num_range: "(0.1, 0.1)",
- ts_range: "(''2010-01-01 14:30'', ''2010-01-01 14:30'')",
- tstz_range: "(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
- int4_range: "(1, 1)",
- int8_range: "(10, 10)")
+ date_range: "[''2012-01-02'', ''2012-01-02'')",
+ num_range: "[0.1, 0.1)",
+ ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')",
+ tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
+ int4_range: "[1, 1)",
+ int8_range: "[10, 10)",
+ float_range: "[0.5, 0.5)")
@new_range = PostgresqlRange.new
@first_range = PostgresqlRange.find(101)
@@ -77,6 +92,12 @@ if ActiveRecord::Base.connection.supports_ranges?
@empty_range = PostgresqlRange.find(105)
end
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.execute 'DROP TYPE IF EXISTS floatrange'
+ reset_connection
+ end
+
def test_data_type_of_range_types
assert_equal :daterange, @first_range.column_for_attribute(:date_range).type
assert_equal :numrange, @first_range.column_for_attribute(:num_range).type
@@ -88,24 +109,24 @@ if ActiveRecord::Base.connection.supports_ranges?
def test_int4range_values
assert_equal 1...11, @first_range.int4_range
- assert_equal 2...10, @second_range.int4_range
- assert_equal 2...Float::INFINITY, @third_range.int4_range
+ assert_equal 1...10, @second_range.int4_range
+ assert_equal 1...Float::INFINITY, @third_range.int4_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
assert_nil @empty_range.int4_range
end
def test_int8range_values
assert_equal 10...101, @first_range.int8_range
- assert_equal 11...100, @second_range.int8_range
- assert_equal 11...Float::INFINITY, @third_range.int8_range
+ assert_equal 10...100, @second_range.int8_range
+ assert_equal 10...Float::INFINITY, @third_range.int8_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
assert_nil @empty_range.int8_range
end
def test_daterange_values
assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range
- assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range
- assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
assert_nil @empty_range.date_range
end
@@ -133,6 +154,14 @@ if ActiveRecord::Base.connection.supports_ranges?
assert_nil @empty_range.tstz_range
end
+ def test_custom_range_values
+ assert_equal 0.5..0.7, @first_range.float_range
+ assert_equal 0.5...0.7, @second_range.float_range
+ assert_equal 0.5...Float::INFINITY, @third_range.float_range
+ assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range
+ assert_nil @empty_range.float_range
+ end
+
def test_create_tstzrange
tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT')
round_trip(@new_range, :tstz_range, tstzrange)
@@ -203,6 +232,38 @@ if ActiveRecord::Base.connection.supports_ranges?
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]") }
+ end
+
private
def assert_equal_round_trip(range, attribute, value)
round_trip(range, attribute, value)
@@ -229,7 +290,8 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range,
tstz_range,
int4_range,
- int8_range
+ int8_range,
+ float_range
) VALUES (
#{values[:id]},
'#{values[:date_range]}',
@@ -237,7 +299,8 @@ if ActiveRecord::Base.connection.supports_ranges?
'#{values[:ts_range]}',
'#{values[:tstz_range]}',
'#{values[:int4_range]}',
- '#{values[:int8_range]}'
+ '#{values[:int8_range]}',
+ '#{values[:float_range]}'
)
SQL
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index d5e1838543..99c26c4bf7 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -27,7 +27,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
set_session_auth
@connection.execute "RESET search_path"
USERS.each do |u|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index e8dd188ec8..11ec7599a3 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -71,7 +71,7 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
- def teardown
+ teardown do
@connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE"
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
@@ -115,6 +115,12 @@ class SchemaTest < ActiveRecord::TestCase
end
end
+ def test_raise_wraped_exception_on_bad_prepare
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.exec_query "select * from developers where id = ?", 'sql', [[nil, 1]]
+ end
+ end
+
def test_schema_change_with_prepared_stmt
altered = false
@connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
@@ -240,6 +246,18 @@ class SchemaTest < ActiveRecord::TestCase
assert_nothing_raised { with_schema_search_path nil }
end
+ def test_index_name_exists
+ with_schema_search_path(SCHEMA_NAME) do
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
+ assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true)
+ end
+ end
+
def test_dump_indexes_for_schema_one
do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
@@ -253,13 +271,13 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_with_uppercase_index_name
- ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"}
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"}
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME
- assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"}
- ActiveRecord::Base.connection.schema_search_path = "public"
+ with_schema_search_path SCHEMA_NAME do
+ assert_nothing_raised { @connection.remove_index! "things", "things_Index"}
+ end
end
def test_primary_key_with_schema_specified
@@ -310,18 +328,17 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_prepared_statements_with_multiple_schemas
+ [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
+ with_schema_search_path schema_name do
+ Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
+ end
+ end
- @connection.schema_search_path = SCHEMA_NAME
- Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
-
- @connection.schema_search_path = SCHEMA2_NAME
- Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now)
-
- @connection.schema_search_path = SCHEMA_NAME
- assert_equal 1, Thing5.count
-
- @connection.schema_search_path = SCHEMA2_NAME
- assert_equal 1, Thing5.count
+ [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
+ with_schema_search_path schema_name do
+ assert_equal 1, Thing5.count
+ end
+ end
end
def test_schema_exists?
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index 89210866f0..4d29a20e66 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -77,7 +77,7 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_bc_timestamp
- date = Date.new(0) - 1.second
+ date = Date.new(0) - 1.week
Developer.create!(:name => "aaron", :updated_at => date)
assert_equal date, Developer.find_by_name("aaron").updated_at
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 3f5d981444..bdf8e15e3e 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -4,31 +4,101 @@ require "cases/helper"
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
+module PostgresqlUUIDHelper
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def enable_uuid_ossp
+ unless connection.extension_enabled?('uuid-ossp')
+ connection.enable_extension 'uuid-ossp'
+ connection.commit_db_transaction
+ end
+
+ connection.reconnect!
+ end
+
+ def drop_table(name)
+ connection.execute "drop table if exists #{name}"
+ end
+end
+
class PostgresqlUUIDTest < ActiveRecord::TestCase
- class UUID < ActiveRecord::Base
- self.table_name = 'pg_uuids'
+ include PostgresqlUUIDHelper
+
+ class UUIDType < ActiveRecord::Base
+ self.table_name = "uuid_data_type"
+ end
+
+ setup do
+ connection.create_table "uuid_data_type" do |t|
+ t.uuid 'guid'
+ end
end
- def setup
- @connection = ActiveRecord::Base.connection
+ teardown do
+ drop_table "uuid_data_type"
+ end
+
+ def test_change_column_default
+ @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
+ UUIDType.reset_column_information
+ column = UUIDType.columns.find { |c| c.name == 'thingy' }
+ assert_equal "uuid_generate_v1()", column.default_function
+
+ @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns.find { |c| c.name == 'thingy' }
+ assert_equal "uuid_generate_v4()", column.default_function
+ end
+
+ def test_data_type_of_uuid_types
+ column = UUIDType.columns_hash["guid"]
+ assert_equal :uuid, column.type
+ assert_equal "uuid", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
+ def test_treat_blank_uuid_as_nil
+ UUIDType.create! guid: ''
+ assert_equal(nil, UUIDType.last.guid)
+ end
+
+ def test_uuid_formats
+ ["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|
+ UUIDType.create(guid: valid_uuid)
+ uuid = UUIDType.last
+ assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid
end
+ end
+end
- @connection.reconnect!
+class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
+ include PostgresqlUUIDHelper
- @connection.transaction do
- @connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
- t.string 'name'
- t.uuid 'other_uuid', default: 'uuid_generate_v4()'
- end
+ class UUID < ActiveRecord::Base
+ self.table_name = 'pg_uuids'
+ end
+
+ setup do
+ enable_uuid_ossp
+
+ connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
+ t.string 'name'
+ t.uuid 'other_uuid', default: 'uuid_generate_v4()'
end
end
- def teardown
- @connection.execute 'drop table if exists pg_uuids'
+ teardown do
+ drop_table "pg_uuids"
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -49,14 +119,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
end
def test_pk_and_sequence_for_uuid_primary_key
- pk, seq = @connection.pk_and_sequence_for('pg_uuids')
+ pk, seq = connection.pk_and_sequence_for('pg_uuids')
assert_equal 'id', pk
assert_equal nil, seq
end
def test_schema_dumper_for_uuid_primary_key
schema = StringIO.new
- ActiveRecord::SchemaDumper.dump(@connection, schema)
+ ActiveRecord::SchemaDumper.dump(connection, schema)
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string)
assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string)
end
@@ -64,34 +134,24 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
end
class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
- class UUID < ActiveRecord::Base
- self.table_name = 'pg_uuids'
- end
+ include PostgresqlUUIDHelper
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.reconnect!
+ setup do
+ enable_uuid_ossp
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
- end
-
- @connection.transaction do
- @connection.create_table('pg_uuids', id: false) do |t|
- t.primary_key :id, :uuid, default: nil
- t.string 'name'
- end
+ connection.create_table('pg_uuids', id: false) do |t|
+ t.primary_key :id, :uuid, default: nil
+ t.string 'name'
end
end
- def teardown
- @connection.execute 'drop table if exists pg_uuids'
+ teardown do
+ drop_table "pg_uuids"
end
if ActiveRecord::Base.connection.supports_extensions?
def test_id_allows_default_override_via_nil
- col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
+ col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
@@ -101,6 +161,8 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
end
class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
+ include PostgresqlUUIDHelper
+
class UuidPost < ActiveRecord::Base
self.table_name = 'pg_uuid_posts'
has_many :uuid_comments, inverse_of: :uuid_post
@@ -111,30 +173,24 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
belongs_to :uuid_post
end
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.reconnect!
-
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
- end
+ setup do
+ enable_uuid_ossp
- @connection.transaction do
- @connection.create_table('pg_uuid_posts', id: :uuid) do |t|
+ connection.transaction do
+ connection.create_table('pg_uuid_posts', id: :uuid) do |t|
t.string 'title'
end
- @connection.create_table('pg_uuid_comments', id: :uuid) do |t|
+ connection.create_table('pg_uuid_comments', id: :uuid) do |t|
t.uuid :uuid_post_id, default: 'uuid_generate_v4()'
t.string 'content'
end
end
end
- def teardown
- @connection.transaction do
- @connection.execute 'drop table if exists pg_uuid_comments'
- @connection.execute 'drop table if exists pg_uuid_posts'
+ teardown do
+ connection.transaction do
+ drop_table "pg_uuid_comments"
+ drop_table "pg_uuid_posts"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
index 66e07b71a0..47b7d38eda 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -1,11 +1,15 @@
require "cases/helper"
-class ViewTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+module ViewTestConcern
+ extend ActiveSupport::Concern
+
+ included do
+ self.use_transactional_fixtures = false
+ mattr_accessor :view_type
+ end
SCHEMA_NAME = 'test_schema'
TABLE_NAME = 'things'
- VIEW_NAME = 'view_things'
COLUMNS = [
'id integer',
'name character varying(50)',
@@ -14,17 +18,19 @@ class ViewTest < ActiveRecord::TestCase
]
class ThingView < ActiveRecord::Base
- self.table_name = 'test_schema.view_things'
end
def setup
+ super
+ ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things"
+
@connection = ActiveRecord::Base.connection
@connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
- @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})"
- @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
+ @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
end
def teardown
+ super
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
@@ -35,7 +41,7 @@ class ViewTest < ActiveRecord::TestCase
def test_column_definitions
assert_nothing_raised do
- assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}")
+ assert_equal COLUMNS, columns(ThingView.table_name)
end
end
@@ -47,3 +53,15 @@ class ViewTest < ActiveRecord::TestCase
end
end
+
+class ViewTest < ActiveRecord::TestCase
+ include ViewTestConcern
+ self.view_type = 'view'
+end
+
+if ActiveRecord::Base.connection.supports_materialized_views?
+ class MaterializedViewTest < ActiveRecord::TestCase
+ include ViewTestConcern
+ self.view_type = 'materialized_view'
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index bf14b378d8..ae299697b1 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -18,12 +18,12 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
end
end
rescue ActiveRecord::StatementInvalid
- return skip "do not test on PG without xml"
+ skip "do not test on PG without xml"
end
@column = XmlDataType.columns.find { |c| c.name == 'payload' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists xml_data_type'
end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index e78cb88562..b478db749d 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class CopyTableTest < ActiveRecord::TestCase
- fixtures :customers, :companies, :comments, :binaries
+ fixtures :customers
def setup
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index b227bce680..f1d6119d2e 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -9,15 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(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" IN (1)), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 1a5c3e7aba..e55525177f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -2,28 +2,22 @@
require "cases/helper"
require 'models/owner'
require 'tempfile'
+require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
class SQLite3AdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+
self.use_transactional_fixtures = false
class DualEncoding < ActiveRecord::Base
end
def setup
- @conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 100
- @conn.execute <<-eosql
- CREATE TABLE items (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
-
- @subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @conn = Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: 100
end
def test_bad_connection
@@ -33,12 +27,11 @@ module ActiveRecord
end
end
- def test_connect_with_url
- skip "can't establish new connection when using memory db" if in_memory_db?
- begin
+ unless in_memory_db?
+ def test_connect_with_url
original_connection = ActiveRecord::Base.remove_connection
tf = Tempfile.open 'whatever'
- url = "sqlite3://#{tf.path}"
+ url = "sqlite3:#{tf.path}"
ActiveRecord::Base.establish_connection(url)
assert ActiveRecord::Base.connection
ensure
@@ -46,13 +39,10 @@ module ActiveRecord
tf.unlink
ActiveRecord::Base.establish_connection(original_connection)
end
- end
- def test_connect_memory_with_url
- skip "can't establish new connection when using memory db" if in_memory_db?
- begin
+ def test_connect_memory_with_url
original_connection = ActiveRecord::Base.remove_connection
- url = "sqlite3:///:memory:"
+ url = "sqlite3::memory:"
ActiveRecord::Base.establish_connection(url)
assert ActiveRecord::Base.connection
ensure
@@ -61,8 +51,10 @@ module ActiveRecord
end
def test_valid_column
- column = @conn.columns('items').find { |col| col.name == 'id' }
- assert @conn.valid_type?(column.type)
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
end
# sqlite databases should be able to support any type and not
@@ -73,13 +65,8 @@ module ActiveRecord
assert @conn.valid_type?(:foobar)
end
- def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
- super
- end
-
def test_column_types
- owner = Owner.create!(:name => "hello".encode('ascii-8bit'))
+ owner = Owner.create!(name: "hello".encode('ascii-8bit'))
owner.reload
select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', '
result = Owner.connection.exec_query <<-esql
@@ -89,23 +76,28 @@ module ActiveRecord
esql
assert(!result.rows.first.include?("blob"), "should not store blobs")
+ ensure
+ owner.delete
end
def test_exec_insert
- column = @conn.columns('items').find { |col| col.name == 'number' }
- vals = [[column, 10]]
- @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals)
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'number' }
+ vals = [[column, 10]]
+ @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals)
- result = @conn.exec_query(
- 'select number from items where number = ?', 'SQL', vals)
+ result = @conn.exec_query(
+ 'select number from ex where number = ?', 'SQL', vals)
- assert_equal 1, result.rows.length
- assert_equal 10, result.rows.first.first
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.first.first
+ end
end
def test_primary_key_returns_nil_for_no_pk
- @conn.exec_query('create table ex(id int, data string)')
- assert_nil @conn.primary_key('ex')
+ with_example_table 'id int, data string' do
+ assert_nil @conn.primary_key('ex')
+ end
end
def test_connection_no_db
@@ -116,17 +108,17 @@ module ActiveRecord
def test_bad_timeout
assert_raises(TypeError) do
- Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 'usa'
+ Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: 'usa'
end
end
# connection is OK with a nil timeout
def test_nil_timeout
- conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => nil
+ conn = Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: nil
assert conn, 'made a connection'
end
@@ -145,77 +137,83 @@ module ActiveRecord
end
def test_exec_no_binds
- @conn.exec_query('create table ex(id int, data string)')
- result = @conn.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
-
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [[1, 'foo']], result.rows
+ with_example_table 'id int, data string' do
+ result = @conn.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
+
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query('SELECT id, data FROM ex')
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [[1, 'foo']], result.rows
+ end
end
def test_exec_query_with_binds
- @conn.exec_query('create table ex(id int, data string)')
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ with_example_table 'id int, data string' do
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
def test_exec_query_typecasts_bind_vals
- @conn.exec_query('create table ex(id int, data string)')
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @conn.columns('ex').find { |col| col.name == 'id' }
+ with_example_table 'id int, data string' do
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
- result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ result = @conn.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
def test_quote_binary_column_escapes_it
DualEncoding.connection.execute(<<-eosql)
- CREATE TABLE dual_encodings (
+ CREATE TABLE IF NOT EXISTS dual_encodings (
id integer PRIMARY KEY AUTOINCREMENT,
name varchar(255),
data binary
)
eosql
str = "\x80".force_encoding("ASCII-8BIT")
- binary = DualEncoding.new :name => 'いただきます!', :data => str
+ binary = DualEncoding.new name: 'いただきます!', data: str
binary.save!
assert_equal str, binary.data
-
ensure
- DualEncoding.connection.drop_table('dual_encodings')
+ DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings')
end
def test_type_cast_should_not_mutate_encoding
name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
+ ensure
+ Owner.delete_all
end
def test_execute
- @conn.execute "INSERT INTO items (number) VALUES (10)"
- records = @conn.execute "SELECT * FROM items"
- assert_equal 1, records.length
-
- record = records.first
- assert_equal 10, record['number']
- assert_equal 1, record['id']
+ with_example_table do
+ @conn.execute "INSERT INTO ex (number) VALUES (10)"
+ records = @conn.execute "SELECT * FROM ex"
+ assert_equal 1, records.length
+
+ record = records.first
+ assert_equal 10, record['number']
+ assert_equal 1, record['id']
+ end
end
def test_quote_string
@@ -223,128 +221,141 @@ module ActiveRecord
end
def test_insert_sql
- 2.times do |i|
- rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
+ with_example_table do
+ 2.times do |i|
+ rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})"
+ assert_equal(i + 1, rv)
+ end
+
+ records = @conn.execute "SELECT * FROM ex"
+ assert_equal 2, records.length
end
-
- records = @conn.execute "SELECT * FROM items"
- assert_equal 2, records.length
end
def test_insert_sql_logged
- sql = "INSERT INTO items (number) VALUES (10)"
- name = "foo"
-
- assert_logged([[sql, name, []]]) do
- @conn.insert_sql sql, name
+ with_example_table do
+ sql = "INSERT INTO ex (number) VALUES (10)"
+ name = "foo"
+ assert_logged [[sql, name, []]] do
+ @conn.insert_sql sql, name
+ end
end
end
def test_insert_id_value_returned
- sql = "INSERT INTO items (number) VALUES (10)"
- idval = 'vuvuzela'
- id = @conn.insert_sql sql, nil, nil, idval
- assert_equal idval, id
+ with_example_table do
+ sql = "INSERT INTO ex (number) VALUES (10)"
+ idval = 'vuvuzela'
+ id = @conn.insert_sql sql, nil, nil, idval
+ assert_equal idval, id
+ end
end
def test_select_rows
- 2.times do |i|
- @conn.create "INSERT INTO items (number) VALUES (#{i})"
+ with_example_table do
+ 2.times do |i|
+ @conn.create "INSERT INTO ex (number) VALUES (#{i})"
+ end
+ rows = @conn.select_rows 'select number, id from ex'
+ assert_equal [[0, 1], [1, 2]], rows
end
- rows = @conn.select_rows 'select number, id from items'
- assert_equal [[0, 1], [1, 2]], rows
end
def test_select_rows_logged
- sql = "select * from items"
- name = "foo"
-
- assert_logged([[sql, name, []]]) do
- @conn.select_rows sql, name
+ with_example_table do
+ sql = "select * from ex"
+ name = "foo"
+ assert_logged [[sql, name, []]] do
+ @conn.select_rows sql, name
+ end
end
end
def test_transaction
- count_sql = 'select count(*) from items'
+ with_example_table do
+ count_sql = 'select count(*) from ex'
- @conn.begin_db_transaction
- @conn.create "INSERT INTO items (number) VALUES (10)"
+ @conn.begin_db_transaction
+ @conn.create "INSERT INTO ex (number) VALUES (10)"
- assert_equal 1, @conn.select_rows(count_sql).first.first
- @conn.rollback_db_transaction
- assert_equal 0, @conn.select_rows(count_sql).first.first
+ assert_equal 1, @conn.select_rows(count_sql).first.first
+ @conn.rollback_db_transaction
+ assert_equal 0, @conn.select_rows(count_sql).first.first
+ end
end
def test_tables
- assert_equal %w{ items }, @conn.tables
-
- @conn.execute <<-eosql
- CREATE TABLE people (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- assert_equal %w{ items people }.sort, @conn.tables.sort
+ with_example_table do
+ assert_equal %w{ ex }, @conn.tables
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do
+ assert_equal %w{ ex people }.sort, @conn.tables.sort
+ end
+ end
end
def test_tables_logs_name
- assert_logged [['SCHEMA', []]] do
+ sql = <<-SQL
+ SELECT name FROM sqlite_master
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ SQL
+ assert_logged [[sql.squish, 'SCHEMA', []]] do
@conn.tables('hello')
- assert_not_nil @subscriber.logged.first.shift
end
end
def test_indexes_logs_name
- assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do
- @conn.indexes('items', 'hello')
+ with_example_table do
+ assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do
+ @conn.indexes('ex', 'hello')
+ end
end
end
def test_table_exists_logs_name
- assert @conn.table_exists?('items')
- assert_equal 'SCHEMA', @subscriber.logged[0][1]
+ with_example_table do
+ sql = <<-SQL
+ SELECT name FROM sqlite_master
+ WHERE type = 'table'
+ AND NOT name = 'sqlite_sequence' AND name = \"ex\"
+ SQL
+ assert_logged [[sql.squish, 'SCHEMA', []]] do
+ assert @conn.table_exists?('ex')
+ end
+ end
end
def test_columns
- columns = @conn.columns('items').sort_by { |x| x.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 }
+ with_example_table do
+ columns = @conn.columns('ex').sort_by { |x| x.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 }
+ end
end
def test_columns_with_default
- @conn.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer default 10
- )
- eosql
- column = @conn.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert_equal 10, column.default
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do
+ column = @conn.columns('ex').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
end
def test_columns_with_not_null
- @conn.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- column = @conn.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert !column.null, "column should not be null"
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do
+ column = @conn.columns('ex').find { |x| x.name == 'number' }
+ assert_not column.null, "column should not be null"
+ end
end
def test_indexes_logs
- assert_difference('@subscriber.logged.length') do
- @conn.indexes('items')
+ with_example_table do
+ assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
+ @conn.indexes('ex')
+ end
end
- assert_match(/items/, @subscriber.logged.last.first)
end
def test_no_indexes
@@ -352,41 +363,45 @@ module ActiveRecord
end
def test_index
- @conn.add_index 'items', 'id', :unique => true, :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ with_example_table do
+ @conn.add_index 'ex', 'id', unique: true, name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
- assert_equal 'items', index.table
- assert index.unique, 'index is unique'
- assert_equal ['id'], index.columns
+ assert_equal 'ex', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
end
def test_non_unique_index
- @conn.add_index 'items', 'id', :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
- assert !index.unique, 'index is not unique'
+ with_example_table do
+ @conn.add_index 'ex', 'id', name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
+ assert_not index.unique, 'index is not unique'
+ end
end
def test_compound_index
- @conn.add_index 'items', %w{ id number }, :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
- assert_equal %w{ id number }.sort, index.columns.sort
+ with_example_table do
+ @conn.add_index 'ex', %w{ id number }, name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
end
def test_primary_key
- assert_equal 'id', @conn.primary_key('items')
-
- @conn.execute <<-eosql
- CREATE TABLE foos (
- internet integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- assert_equal 'internet', @conn.primary_key('foos')
+ with_example_table do
+ assert_equal 'id', @conn.primary_key('ex')
+ with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do
+ assert_equal 'internet', @conn.primary_key('foos')
+ end
+ end
end
def test_no_primary_key
- @conn.execute 'CREATE TABLE failboat (number integer not null)'
- assert_nil @conn.primary_key('failboat')
+ with_example_table 'number integer not null' do
+ assert_nil @conn.primary_key('ex')
+ end
end
def test_supports_extensions
@@ -403,15 +418,15 @@ module ActiveRecord
def test_statement_closed
db = SQLite3::Database.new(ActiveRecord::Base.
- configurations['arunit']['database'])
+ configurations['arunit']['database'])
statement = SQLite3::Statement.new(db,
- 'CREATE TABLE statement_test (number integer not null)')
+ 'CREATE TABLE statement_test (number integer not null)')
statement.stubs(:step).raises(SQLite3::BusyException, 'busy')
statement.stubs(:columns).once.returns([])
statement.expects(:close).once
SQLite3::Statement.stubs(:new).returns(statement)
- assert_raise ActiveRecord::StatementInvalid do
+ assert_raises ActiveRecord::StatementInvalid do
@conn.exec_query 'select * from statement_test'
end
end
@@ -419,10 +434,21 @@ module ActiveRecord
private
def assert_logged logs
+ subscriber = SQLSubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber)
yield
- assert_equal logs, @subscriber.logged
+ assert_equal logs, subscriber.logged
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
end
+ def with_example_table(definition = nil, table_name = 'ex', &block)
+ definition ||= <<-SQL
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ SQL
+ super(@conn, table_name, definition, &block)
+ end
end
end
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 500df52cd8..811695938e 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -10,7 +10,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::SchemaMigration.drop_table
end
- def teardown
+ teardown do
@connection.drop_table :fruits rescue nil
@connection.drop_table :nep_fruits rescue nil
@connection.drop_table :nep_schema_migrations rescue nil
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index d38648202e..3e0032ec73 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -6,9 +6,15 @@ module ActiveRecord
module Associations
class AssociationScopeTest < ActiveRecord::TestCase
test 'does not duplicate conditions' do
- association_scope = AssociationScope.new(Author.new.association(:welcome_posts))
- wheres = association_scope.scope.where_values.map(&:right)
+ scope = AssociationScope.scope(Author.new.association(:welcome_posts),
+ Author.connection)
+ wheres = scope.where_values.map(&:right)
+ binds = scope.bind_values.map(&:last)
+ wheres = scope.where_values.map(&:right).reject { |node|
+ Arel::Nodes::BindParam === node
+ }
assert_equal wheres.uniq, wheres
+ assert_equal binds.uniq, binds
end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 3205d0c28b..3b484a0d64 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -16,6 +16,8 @@ require 'models/essay'
require 'models/toy'
require 'models/invoice'
require 'models/line_item'
+require 'models/column'
+require 'models/record'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -28,6 +30,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:first_firm).name, firm.name
end
+ def test_belongs_to_does_not_use_order_by
+ ActiveRecord::SQLCounter.clear_log
+ Client.find(3).firm
+ ensure
+ assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query'
+ end
+
def test_belongs_to_with_primary_key
client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)
assert_equal companies(:first_firm).name, client.firm_with_primary_key.name
@@ -224,13 +233,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_counter
debate = Topic.create("title" => "debate")
- assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
+ assert_equal 0, debate.read_attribute("replies_count"), "No replies yet"
trash = debate.replies.create("title" => "blah!", "content" => "world around!")
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
+ assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created"
trash.destroy
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
+ assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted"
end
def test_belongs_to_counter_with_assigning_nil
@@ -333,6 +342,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { line_item.touch }
end
+ def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes
+ assert_not LineItem.column_names.include?("updated_at")
+
+ line_item = LineItem.create!
+ invoice = Invoice.create!(line_items: [line_item])
+ initial = invoice.updated_at
+ line_item.touch
+
+ assert_not_equal initial, invoice.reload.updated_at
+ end
+
def test_belongs_to_with_touch_option_on_touch_and_removed_parent
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
@@ -482,6 +502,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 4, topic.replies.size
end
+ def test_concurrent_counter_cache_double_destroy
+ topic = Topic.create :title => "Zoom-zoom-zoom"
+
+ 5.times do
+ topic.replies.create(:title => "re: zoom", :content => "speedy quick!")
+ end
+
+ assert_equal 5, topic.reload[:replies_count]
+ assert_equal 5, topic.replies.size
+
+ reply = topic.replies.first
+ reply_clone = Reply.find(reply.id)
+
+ reply.destroy
+ assert_equal 4, topic.reload[:replies_count]
+
+ reply_clone.destroy
+ assert_equal 4, topic.reload[:replies_count]
+ assert_equal 4, topic.replies.size
+ end
+
def test_custom_counter_cache
reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
assert_equal 0, reply[:replies_count]
@@ -817,6 +858,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, comments(:greetings).reload.children_count
end
+ def test_belongs_to_with_id_assigning
+ post = posts(:welcome)
+ comment = Comment.create! body: "foo", post: post
+ parent = comments(:greetings)
+ assert_equal 0, parent.reload.children_count
+ comment.parent_id = parent.id
+
+ comment.save!
+ assert_equal 1, parent.reload.children_count
+ end
+
def test_polymorphic_with_custom_primary_key
toy = Toy.create!
sponsor = Sponsor.create!(:sponsorable => toy)
@@ -846,4 +898,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert post.save
assert_equal post.author_id, author2.id
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
+ Class.new(ActiveRecord::Base) do
+ belongs_to name
+ end
+ end
+ end
+ end
+
+ test 'belongs_to works with model called Record' do
+ record = Record.create!
+ Column.create! record: record
+ assert_equal 1, Column.count
+ end
end
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 2d0d4541b4..968f36e92c 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -101,6 +101,27 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
"after_adding#{david.id}"], ar.developers_log
end
+ def test_has_and_belongs_to_many_before_add_called_before_save
+ dev = nil
+ new_dev = nil
+ klass = Class.new(Project) do
+ def self.name; Project.name; end
+ has_and_belongs_to_many :developers_with_callbacks,
+ :class_name => "Developer",
+ :before_add => lambda { |o,r|
+ dev = r
+ new_dev = r.new_record?
+ }
+ end
+ rec = klass.create!
+ alice = Developer.new(:name => 'alice')
+ rec.developers_with_callbacks << alice
+ assert_equal alice, dev
+ assert_not_nil new_dev
+ assert new_dev, "record should not have been saved"
+ assert_not alice.new_record?
+ end
+
def test_has_and_belongs_to_many_after_add_called_after_save
ar = projects(:active_record)
assert ar.developers_log.empty?
@@ -129,7 +150,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
"after_removing#{jamis.id}"], activerecord.developers_log
end
- def test_has_and_belongs_to_many_remove_callback_on_clear
+ def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear
activerecord = projects(:active_record)
assert activerecord.developers_log.empty?
if activerecord.developers_with_callbacks.size == 0
@@ -138,9 +159,9 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
activerecord.reload
assert activerecord.developers_with_callbacks.size == 2
end
- log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
+ log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort
assert activerecord.developers_with_callbacks.clear
- assert_equal log_array, activerecord.developers_log.sort
+ assert_predicate activerecord.developers_log, :empty?
end
def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
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 5ff117eaa0..0ff87d53ea 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -68,7 +68,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
generate_test_object_graphs
end
- def teardown
+ teardown do
[Circle, Square, Triangle, PaintColor, PaintTexture,
ShapeExpression, NonPolyOne, NonPolyTwo].each do |c|
c.delete_all
@@ -111,7 +111,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
@first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post)
end
- def teardown
+ teardown do
@davey_mcdave.destroy
@first_post.destroy
@first_comment.destroy
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 634f6b63ba..a61a070331 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -1,128 +1,132 @@
require "cases/helper"
-class Virus < ActiveRecord::Base
- belongs_to :octopus
-end
-class Octopus < ActiveRecord::Base
- has_one :virus
-end
-class Pass < ActiveRecord::Base
- belongs_to :bus
-end
-class Bus < ActiveRecord::Base
- has_many :passes
-end
-class Mess < ActiveRecord::Base
- has_and_belongs_to_many :crises
-end
-class Crisis < ActiveRecord::Base
- has_and_belongs_to_many :messes
- has_many :analyses, :dependent => :destroy
- has_many :successes, :through => :analyses
- has_many :dresses, :dependent => :destroy
- has_many :compresses, :through => :dresses
-end
-class Analysis < ActiveRecord::Base
- belongs_to :crisis
- belongs_to :success
-end
-class Success < ActiveRecord::Base
- has_many :analyses, :dependent => :destroy
- has_many :crises, :through => :analyses
-end
-class Dress < ActiveRecord::Base
- belongs_to :crisis
- has_many :compresses
-end
-class Compress < ActiveRecord::Base
- belongs_to :dress
-end
-
+if ActiveRecord::Base.connection.supports_migrations?
class EagerSingularizationTest < ActiveRecord::TestCase
+ class Virus < ActiveRecord::Base
+ belongs_to :octopus
+ end
+
+ class Octopus < ActiveRecord::Base
+ has_one :virus
+ end
+
+ class Pass < ActiveRecord::Base
+ belongs_to :bus
+ end
+
+ class Bus < ActiveRecord::Base
+ has_many :passes
+ end
+
+ class Mess < ActiveRecord::Base
+ has_and_belongs_to_many :crises
+ end
+
+ class Crisis < ActiveRecord::Base
+ has_and_belongs_to_many :messes
+ has_many :analyses, :dependent => :destroy
+ has_many :successes, :through => :analyses
+ has_many :dresses, :dependent => :destroy
+ has_many :compresses, :through => :dresses
+ end
+
+ class Analysis < ActiveRecord::Base
+ belongs_to :crisis
+ belongs_to :success
+ end
+
+ class Success < ActiveRecord::Base
+ has_many :analyses, :dependent => :destroy
+ has_many :crises, :through => :analyses
+ end
+
+ class Dress < ActiveRecord::Base
+ belongs_to :crisis
+ has_many :compresses
+ end
+
+ class Compress < ActiveRecord::Base
+ belongs_to :dress
+ end
def setup
- if ActiveRecord::Base.connection.supports_migrations?
- ActiveRecord::Base.connection.create_table :viri do |t|
- t.column :octopus_id, :integer
- t.column :species, :string
- end
- ActiveRecord::Base.connection.create_table :octopi do |t|
- t.column :species, :string
- end
- ActiveRecord::Base.connection.create_table :passes do |t|
- t.column :bus_id, :integer
- t.column :rides, :integer
- end
- ActiveRecord::Base.connection.create_table :buses do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t|
- t.column :crisis_id, :integer
- t.column :mess_id, :integer
- end
- ActiveRecord::Base.connection.create_table :messes do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :crises do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :successes do |t|
- t.column :name, :string
- end
- ActiveRecord::Base.connection.create_table :analyses do |t|
- t.column :crisis_id, :integer
- t.column :success_id, :integer
- end
- ActiveRecord::Base.connection.create_table :dresses do |t|
- t.column :crisis_id, :integer
- end
- ActiveRecord::Base.connection.create_table :compresses do |t|
- t.column :dress_id, :integer
- end
- @have_tables = true
- else
- @have_tables = false
- end
- end
-
- def teardown
- ActiveRecord::Base.connection.drop_table :viri
- ActiveRecord::Base.connection.drop_table :octopi
- ActiveRecord::Base.connection.drop_table :passes
- ActiveRecord::Base.connection.drop_table :buses
- ActiveRecord::Base.connection.drop_table :crises_messes
- ActiveRecord::Base.connection.drop_table :messes
- ActiveRecord::Base.connection.drop_table :crises
- ActiveRecord::Base.connection.drop_table :successes
- ActiveRecord::Base.connection.drop_table :analyses
- ActiveRecord::Base.connection.drop_table :dresses
- ActiveRecord::Base.connection.drop_table :compresses
+ connection.create_table :viri do |t|
+ t.column :octopus_id, :integer
+ t.column :species, :string
+ end
+ connection.create_table :octopi do |t|
+ t.column :species, :string
+ end
+ connection.create_table :passes do |t|
+ t.column :bus_id, :integer
+ t.column :rides, :integer
+ end
+ connection.create_table :buses do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises_messes, :id => false do |t|
+ t.column :crisis_id, :integer
+ t.column :mess_id, :integer
+ end
+ connection.create_table :messes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :crises do |t|
+ t.column :name, :string
+ end
+ connection.create_table :successes do |t|
+ t.column :name, :string
+ end
+ connection.create_table :analyses do |t|
+ t.column :crisis_id, :integer
+ t.column :success_id, :integer
+ end
+ connection.create_table :dresses do |t|
+ t.column :crisis_id, :integer
+ end
+ connection.create_table :compresses do |t|
+ t.column :dress_id, :integer
+ end
+ end
+
+ teardown do
+ connection.drop_table :viri
+ connection.drop_table :octopi
+ connection.drop_table :passes
+ connection.drop_table :buses
+ connection.drop_table :crises_messes
+ connection.drop_table :messes
+ connection.drop_table :crises
+ connection.drop_table :successes
+ connection.drop_table :analyses
+ connection.drop_table :dresses
+ connection.drop_table :compresses
+ end
+
+ def connection
+ ActiveRecord::Base.connection
end
def test_eager_no_extra_singularization_belongs_to
- return unless @have_tables
assert_nothing_raised do
Virus.all.merge!(:includes => :octopus).to_a
end
end
def test_eager_no_extra_singularization_has_one
- return unless @have_tables
assert_nothing_raised do
Octopus.all.merge!(:includes => :virus).to_a
end
end
def test_eager_no_extra_singularization_has_many
- return unless @have_tables
assert_nothing_raised do
Bus.all.merge!(:includes => :passes).to_a
end
end
def test_eager_no_extra_singularization_has_and_belongs_to_many
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :messes).to_a
Mess.all.merge!(:includes => :crises).to_a
@@ -130,16 +134,15 @@ class EagerSingularizationTest < ActiveRecord::TestCase
end
def test_eager_no_extra_singularization_has_many_through_belongs_to
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :successes).to_a
end
end
def test_eager_no_extra_singularization_has_many_through_has_many
- return unless @have_tables
assert_nothing_raised do
Crisis.all.merge!(:includes => :compresses).to_a
end
end
end
+end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 498a4e8144..7eaa5adc86 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -235,6 +235,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_finding_with_includes_on_empty_polymorphic_type_column
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ sponsor.update!(sponsorable_type: '', sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL
+ sponsor = assert_queries(1) do
+ assert_nothing_raised { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) }
+ end
+ assert_no_queries do
+ assert_equal nil, sponsor.sponsorable
+ end
+ end
+
def test_loading_from_an_association
posts = authors(:david).posts.merge(:includes => :comments, :order => "posts.id").to_a
assert_equal 2, posts.first.comments.size
@@ -407,19 +418,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_load_has_one_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael))
+ michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael).id)
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
end
def test_eager_load_has_many_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :references).find(people(:michael))
+ michael = Person.all.merge!(:includes => :references).find(people(:michael).id)
references(:michael_magician,:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
end
def test_eager_load_has_many_through_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :jobs).find(people(:michael))
+ michael = Person.all.merge!(:includes => :jobs).find(people(:michael).id)
jobs(:magician, :unicyclist)
assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
end
@@ -709,16 +720,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_invalid_association_reference
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=> :monkeys ).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=>[ :monkeys ]).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=>[ 'monkeys' ]).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
Post.all.merge!(:includes=>[ :monkeys, :elephants ]).find(6)
}
end
@@ -1187,6 +1198,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:bob), author
end
+ test "preloading with a polymorphic association and using the existential predicate but also using a select" do
+ assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
+
+ assert_nothing_raised do
+ authors(:david).essays.includes(:writer).select(:name).any?
+ end
+ end
+
test "preloading with a polymorphic association and using the existential predicate" do
assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
@@ -1194,4 +1213,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
authors(:david).essays.includes(:writer).any?
end
end
+
+ test "preloading associations with string joins and order references" do
+ author = assert_queries(2) {
+ Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first
+ }
+ assert_no_queries {
+ assert_equal 5, author.posts.size
+ }
+ end
+
+ test "including associations with where.not adds implicit references" do
+ author = assert_queries(2) {
+ Author.includes(:posts).where.not(posts: { title: 'Welcome to the weblog'} ).last
+ }
+
+ assert_no_queries {
+ assert_equal 2, author.posts.size
+ }
+ 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 8aee7ff40e..5d33634da2 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
@@ -11,6 +11,7 @@ require 'models/author'
require 'models/tag'
require 'models/tagging'
require 'models/parrot'
+require 'models/person'
require 'models/pirate'
require 'models/treasure'
require 'models/price_estimate'
@@ -20,6 +21,7 @@ require 'models/membership'
require 'models/sponsor'
require 'models/country'
require 'models/treaty'
+require 'models/vertex'
require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
@@ -775,6 +777,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert project.developers.include?(developer)
end
+ def test_destruction_does_not_error_without_primary_key
+ redbeard = pirates(:redbeard)
+ george = parrots(:george)
+ redbeard.parrots << george
+ assert_equal 2, george.pirates.count
+ Pirate.includes(:parrots).where(parrot: redbeard.parrot).find(redbeard.id).destroy
+ assert_equal 1, george.pirates.count
+ assert_equal [], Pirate.where(id: redbeard.id)
+ end
+
test "has and belongs to many associations on new records use null relations" do
projects = Developer.new.projects
assert_no_queries do
@@ -785,4 +797,31 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_create
+ rich_person = RichPerson.new
+
+ treasure = Treasure.new
+ treasure.rich_people << rich_person
+ treasure.valid?
+
+ assert_equal 1, treasure.rich_people.size
+ assert_nil rich_person.first_name, 'should not run associated person validation on create when validate: false'
+ end
+
+ def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_update
+ rich_person = RichPerson.create!
+ person_first_name = rich_person.first_name
+ assert_not_nil person_first_name
+
+ treasure = Treasure.new
+ treasure.rich_people << rich_person
+ treasure.valid?
+
+ assert_equal 1, treasure.rich_people.size
+ assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false'
+ end
+
+ def test_custom_join_table
+ assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table
+ 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 e45efb0161..2f5c9d6e1b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -22,6 +22,10 @@ require 'models/engine'
require 'models/categorization'
require 'models/minivan'
require 'models/speedometer'
+require 'models/reference'
+require 'models/job'
+require 'models/college'
+require 'models/student'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -39,7 +43,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
:people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations
+ :categorizations, :jobs
def setup
Client.destroyed_client_ids.clear
@@ -63,6 +67,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
dev.developer_projects.map(&:project_id).sort
end
+ def test_has_many_build_with_options
+ college = College.create(name: 'UFMT')
+ Student.create(active: true, college_id: college.id, name: 'Sarah')
+
+ assert_equal college.students, Student.where(active: true, college_id: college.id)
+ end
+
def test_create_from_association_should_respect_default_scope
car = Car.create(:name => 'honda')
assert_equal 'honda', car.name
@@ -107,6 +118,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy"
end
+ def test_delete_all_on_association_is_the_same_as_not_loaded
+ author = authors :david
+ author.thinking_posts.create!(:body => "test")
+ author.reload
+ expected_sql = capture_sql { author.thinking_posts.delete_all }
+
+ author.thinking_posts.create!(:body => "test")
+ author.reload
+ author.thinking_posts.inspect
+ loaded_sql = capture_sql { author.thinking_posts.delete_all }
+ assert_equal(expected_sql, loaded_sql)
+ end
+
+ def test_delete_all_on_association_with_nil_dependency_is_the_same_as_not_loaded
+ author = authors :david
+ author.posts.create!(:title => "test", :body => "body")
+ author.reload
+ expected_sql = capture_sql { author.posts.delete_all }
+
+ author.posts.create!(:title => "test", :body => "body")
+ author.reload
+ author.posts.to_a
+ loaded_sql = capture_sql { author.posts.delete_all }
+ assert_equal(expected_sql, loaded_sql)
+ end
+
def test_building_the_associated_object_with_implicit_sti_base_class
firm = DependentFirm.new
company = firm.companies.build
@@ -216,6 +253,31 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_no_queries do
+ bulbs.second()
+ bulbs.second({})
+ end
+
+ assert_no_queries do
+ bulbs.third()
+ bulbs.third({})
+ end
+
+ assert_no_queries do
+ bulbs.fourth()
+ bulbs.fourth({})
+ end
+
+ assert_no_queries do
+ bulbs.fifth()
+ bulbs.fifth({})
+ end
+
+ assert_no_queries do
+ bulbs.forty_two()
+ bulbs.forty_two({})
+ end
+
+ assert_no_queries do
bulbs.last()
bulbs.last({})
end
@@ -242,11 +304,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
def test_counting_with_counter_sql
- assert_equal 2, Firm.all.merge!(:order => "id").first.clients.count
+ assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count
end
def test_counting
- assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count
+ assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count
end
def test_counting_with_single_hash
@@ -254,7 +316,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_counting_with_column_name_and_hash
- assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count(:name)
+ assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count(:name)
end
def test_counting_with_association_limit
@@ -264,17 +326,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding
- assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length
+ assert_equal 3, Firm.all.merge!(:order => "id").first.clients.length
end
def test_finding_array_compatibility
- assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length
+ assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length
end
def test_find_many_with_merged_options
assert_equal 1, companies(:first_firm).limited_clients.size
assert_equal 1, companies(:first_firm).limited_clients.to_a.size
- assert_equal 2, companies(:first_firm).limited_clients.limit(nil).to_a.size
+ assert_equal 3, companies(:first_firm).limited_clients.limit(nil).to_a.size
end
def test_find_should_append_to_association_order
@@ -283,8 +345,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_dynamic_find_should_respect_association_order
- assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first
- assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
+ assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first
+ assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
def test_cant_save_has_many_readonly_association
@@ -297,7 +359,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding_with_different_class_name_and_order
- assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name
+ assert_equal "Apex", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name
end
def test_finding_with_foreign_key
@@ -355,7 +417,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_all
firm = Firm.all.merge!(:order => "id").first
- assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length
+ assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length
assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length
end
@@ -364,7 +426,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert ! firm.clients.loaded?
- assert_queries(3) do
+ assert_queries(4) do
firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id }
end
@@ -434,15 +496,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_grouped
all_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1").to_a
grouped_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').to_a
- assert_equal 2, all_clients_of_firm1.size
+ assert_equal 3, all_clients_of_firm1.size
assert_equal 1, grouped_clients_of_firm1.size
end
def test_find_scoped_grouped
assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size
assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length
- assert_equal 2, companies(:first_firm).clients_grouped_by_name.size
- assert_equal 2, companies(:first_firm).clients_grouped_by_name.length
+ assert_equal 3, companies(:first_firm).clients_grouped_by_name.size
+ assert_equal 3, companies(:first_firm).clients_grouped_by_name.length
end
def test_find_scoped_grouped_having
@@ -462,25 +524,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id)
end
- def test_select_without_foreign_key
+ def test_select_without_foreign_key
assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit
- end
+ end
def test_adding
force_signal37_to_load_all_clients_of_firm
natural = Client.new("name" => "Natural Company")
companies(:first_firm).clients_of_firm << natural
- assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db
+ assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection
+ assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db
assert_equal natural, companies(:first_firm).clients_of_firm.last
end
def test_adding_using_create
first_firm = companies(:first_firm)
- assert_equal 2, first_firm.plain_clients.size
- first_firm.plain_clients.create(:name => "Natural Company")
- assert_equal 3, first_firm.plain_clients.length
assert_equal 3, first_firm.plain_clients.size
+ first_firm.plain_clients.create(:name => "Natural Company")
+ assert_equal 4, first_firm.plain_clients.length
+ assert_equal 4, first_firm.plain_clients.size
end
def test_create_with_bang_on_has_many_when_parent_is_new_raises
@@ -519,8 +581,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_adding_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
- assert_equal 3, companies(:first_firm).clients_of_firm.size
- assert_equal 3, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm.size
+ assert_equal 4, companies(:first_firm).clients_of_firm(true).size
end
def test_transactions_when_adding_to_persisted
@@ -573,7 +635,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
company = companies(:first_firm) # company already has one client
company.clients_of_firm.build("name" => "Another Client")
company.clients_of_firm.build("name" => "Yet Another Client")
- assert_equal 3, company.clients_of_firm.size
+ assert_equal 4, company.clients_of_firm.size
end
def test_collection_not_empty_after_building
@@ -649,14 +711,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Firm.column_names
Client.column_names
- assert_equal 1, first_firm.clients_of_firm.size
+ assert_equal 2, first_firm.clients_of_firm.size
first_firm.clients_of_firm.reset
assert_queries(1) do
first_firm.clients_of_firm.create(:name => "Superstars")
end
- assert_equal 2, first_firm.clients_of_firm.size
+ assert_equal 3, first_firm.clients_of_firm.size
end
def test_create
@@ -669,7 +731,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_many
companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
- assert_equal 3, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm(true).size
end
def test_create_followed_by_save_does_not_load_target
@@ -681,8 +743,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
- assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_deleting_before_save
@@ -779,8 +841,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert_equal 2, companies(:first_firm).clients_of_firm.size
- companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
+ assert_equal 3, companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]])
assert_equal 0, companies(:first_firm).clients_of_firm.size
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
end
@@ -789,7 +851,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client")
clients = companies(:first_firm).dependent_clients_of_firm.to_a
- assert_equal 2, clients.count
+ assert_equal 3, clients.count
assert_difference "Client.count", -(clients.count) do
companies(:first_firm).dependent_clients_of_firm.delete_all
@@ -799,7 +861,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all_with_not_yet_loaded_association_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert_equal 2, companies(:first_firm).clients_of_firm.size
+ assert_equal 3, companies(:first_firm).clients_of_firm.size
companies(:first_firm).clients_of_firm.reset
companies(:first_firm).clients_of_firm.delete_all
assert_equal 0, companies(:first_firm).clients_of_firm.size
@@ -832,7 +894,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_clearing_an_association_collection
firm = companies(:first_firm)
client_id = firm.clients_of_firm.first.id
- assert_equal 1, firm.clients_of_firm.size
+ assert_equal 2, firm.clients_of_firm.size
firm.clients_of_firm.clear
@@ -866,7 +928,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_clearing_a_dependent_association_collection
firm = companies(:first_firm)
client_id = firm.dependent_clients_of_firm.first.id
- assert_equal 1, firm.dependent_clients_of_firm.size
+ assert_equal 2, firm.dependent_clients_of_firm.size
assert_equal 1, Client.find_by_id(client_id).client_of
# :delete_all is called on each client since the dependent options is :destroy
@@ -897,7 +959,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_clearing_an_exclusively_dependent_association_collection
firm = companies(:first_firm)
client_id = firm.exclusively_dependent_clients_of_firm.first.id
- assert_equal 1, firm.exclusively_dependent_clients_of_firm.size
+ assert_equal 2, firm.exclusively_dependent_clients_of_firm.size
assert_equal [], Client.destroyed_client_ids[firm.id]
@@ -953,10 +1015,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_delete_all_association_with_primary_key_deletes_correct_records
firm = Firm.first
# break the vanilla firm_id foreign key
- assert_equal 2, firm.clients.count
+ assert_equal 3, firm.clients.count
firm.clients.first.update_columns(firm_id: nil)
- assert_equal 1, firm.clients(true).count
- assert_equal 1, firm.clients_using_primary_key_with_delete_all.count
+ assert_equal 2, firm.clients(true).count
+ assert_equal 2, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
firm = Firm.first
firm.destroy
@@ -988,8 +1050,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
summit = Client.find_by_name('Summit')
companies(:first_firm).clients_of_firm.delete(summit)
- assert_equal 1, companies(:first_firm).clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm.size
+ assert_equal 2, companies(:first_firm).clients_of_firm(true).size
assert_equal 2, summit.client_of
end
@@ -1026,8 +1088,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first)
end
- assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_destroying_by_fixnum_id
@@ -1037,8 +1099,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
end
- assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_destroying_by_string_id
@@ -1048,21 +1110,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
end
- assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_destroying_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert_equal 2, companies(:first_firm).clients_of_firm.size
+ assert_equal 3, companies(:first_firm).clients_of_firm.size
assert_difference "Client.count", -2 do
companies(:first_firm).clients_of_firm.destroy([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
end
- assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
end
def test_destroy_all
@@ -1078,7 +1140,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_dependence
firm = companies(:first_firm)
- assert_equal 2, firm.clients.size
+ assert_equal 3, firm.clients.size
firm.destroy
assert Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.empty?
end
@@ -1091,14 +1153,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_dependent_when_deleted_from_association
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
firm = Firm.all.merge!(:order => "id").first
- assert_equal 2, firm.clients.size
+ assert_equal 3, firm.clients.size
client = firm.clients.first
firm.clients.delete(client)
assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) }
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) }
- assert_equal 1, firm.clients.size
+ assert_equal 2, firm.clients.size
end
def test_three_levels_of_dependence
@@ -1113,12 +1175,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_dependence_with_transaction_support_on_failure
firm = companies(:first_firm)
clients = firm.clients
- assert_equal 2, clients.length
+ assert_equal 3, clients.length
clients.last.instance_eval { def overwrite_to_raise() raise "Trigger rollback" end }
firm.destroy rescue "do nothing"
- assert_equal 2, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size
+ assert_equal 3, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size
end
def test_dependence_on_account
@@ -1217,6 +1279,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal orig_accounts, firm.accounts
end
+ def test_replace_with_same_content
+ firm = Firm.first
+ firm.clients = []
+ firm.save
+
+ assert_queries(0, ignore_none: true) do
+ firm.clients = []
+ end
+ end
+
def test_transactions_when_replacing_on_persisted
good = Client.new(:name => "Good")
bad = Client.new(:name => "Bad", :raise_on_save => true)
@@ -1239,7 +1311,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_get_ids
- assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
+ assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], companies(:first_firm).client_ids
end
def test_get_ids_for_loaded_associations
@@ -1254,7 +1326,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
company = companies(:first_firm)
assert !company.clients.loaded?
- assert_equal [companies(:first_client).id, companies(:second_client).id], company.client_ids
+ assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids
assert !company.clients.loaded?
end
@@ -1263,7 +1335,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_get_ids_for_ordered_association
- assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
+ assert_equal [companies(:another_first_firm_client).id, companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
end
def test_get_ids_for_association_on_new_record_does_not_try_to_find_records
@@ -1357,9 +1429,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal false, firm.clients.include?(client)
end
- def test_calling_first_or_last_on_association_should_not_load_association
+ def test_calling_first_nth_or_last_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.first
+ firm.clients.second
firm.clients.last
assert !firm.clients.loaded?
end
@@ -1384,30 +1457,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries 1 do
firm.clients.first
+ firm.clients.second
firm.clients.last
end
assert firm.clients.loaded?
end
- def test_calling_first_or_last_on_existing_record_with_create_should_not_load_association
+ def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(:name => 'Foo')
assert !firm.clients.loaded?
- assert_queries 2 do
+ assert_queries 3 do
firm.clients.first
+ firm.clients.second
firm.clients.last
end
assert !firm.clients.loaded?
end
- def test_calling_first_or_last_on_new_record_should_not_run_queries
+ def test_calling_first_nth_or_last_on_new_record_should_not_run_queries
firm = Firm.new
assert_no_queries do
firm.clients.first
+ firm.clients.second
firm.clients.last
end
end
@@ -1494,7 +1570,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_many_should_return_true_if_more_than_one
firm = companies(:first_firm)
assert firm.clients.many?
- assert_equal 2, firm.clients.size
+ assert_equal 3, firm.clients.size
end
def test_joins_with_namespaced_model_should_use_correct_type
@@ -1791,4 +1867,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
topic.approved_replies.create!
end
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
+ Class.new(ActiveRecord::Base) do
+ has_many name
+ end
+ end
+ end
+ 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 47592f312e..2e62189e7a 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -698,9 +698,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
[:added, :before, "Roger"],
[:added, :after, "Roger"]
], log.last(4)
-
- post.people_with_callbacks.clear
- assert_equal((%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort)
end
def test_dynamic_find_should_respect_association_include
@@ -817,6 +814,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert author.named_categories(true).include?(category)
end
+ def test_collection_exists
+ author = authors(:mary)
+ category = Category.create!(author_ids: [author.id], name: "Primary")
+ assert category.authors.exists?(id: author.id)
+ assert category.reload.authors.exists?(id: author.id)
+ end
+
def test_collection_delete_with_nonstandard_primary_key_on_belongs_to
author = authors(:mary)
category = author.named_categories.create(:name => "Primary")
@@ -1075,7 +1079,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
- test "has many through associations on new records use null relations" do
+ def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
assert_no_queries do
@@ -1087,7 +1091,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- test "has many through with default scope on the target" do
+ def test_has_many_through_with_default_scope_on_the_target
person = people(:michael)
assert_equal [posts(:thinking)], person.first_posts
@@ -1098,4 +1102,28 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_with_includes_in_through_association_scope
assert_not_empty posts(:welcome).author_address_extra_with_address
end
+
+ def test_insert_records_via_has_many_through_association_with_scope
+ club = Club.create!
+ member = Member.create!
+ Membership.create!(club: club, member: member)
+
+ club.favourites << member
+ assert_equal [member], club.favourites
+
+ club.reload
+ assert_equal [member], club.favourites
+ end
+
+ def test_has_many_through_unscope_default_scope
+ post = Post.create!(:title => 'Beaches', :body => "I like beaches!")
+ Reader.create! :person => people(:david), :post => post
+ LazyReader.create! :person => people(:susan), :post => post
+
+ assert_equal 2, post.people.to_a.size
+ assert_equal 1, post.lazy_people.to_a.size
+
+ assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size
+ assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size
+ 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 5a41461edf..a4650ccdf2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -22,6 +22,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
end
+ def test_has_one_does_not_use_order_by
+ ActiveRecord::SQLCounter.clear_log
+ companies(:first_firm).account
+ ensure
+ assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query'
+ end
+
def test_has_one_cache_nils
firm = companies(:another_firm)
assert_queries(1) { assert_nil firm.account }
@@ -557,4 +564,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
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
+ Class.new(ActiveRecord::Base) do
+ has_one name
+ end
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index a9efa6d86a..b23517b2f9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -117,4 +117,13 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_equal [author], Author.where(id: author).joins(:special_categorizations)
end
+
+ test "the default scope of the target is correctly aliased when joining associations" do
+ author = Author.create! name: "Jon"
+ author.categories.create! name: 'Not Special'
+ author.special_categories.create! name: 'Special'
+
+ categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a
+ assert_equal 2, categories.size
+ end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 48e6fc5cd4..f663b5490c 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -255,6 +255,15 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal man, man.interests.where("1=1").first.man
end
end
+
+ def test_reset_unloads_target
+ david = authors(:david)
+ david.posts.reload
+
+ assert david.posts.loaded?
+ david.posts.reset
+ assert !david.posts.loaded?
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 6c581a432f..38e93288e4 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -22,7 +22,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
@target.table_name = 'topics'
end
- def teardown
+ teardown do
ActiveRecord::Base.send(:attribute_method_matchers).clear
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
@@ -288,10 +288,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_read_attribute
topic = Topic.new
topic.title = "Don't change the topic"
- assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic.read_attribute("title")
assert_equal "Don't change the topic", topic["title"]
- assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic.read_attribute(:title)
assert_equal "Don't change the topic", topic[:title]
end
@@ -358,10 +358,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
super(attr_name).upcase
end
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title")
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title")
assert_equal "STOP CHANGING THE TOPIC", topic["title"]
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title)
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title)
assert_equal "STOP CHANGING THE TOPIC", topic[:title]
end
@@ -555,6 +555,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_converted_values_are_returned_after_assignment
+ developer = Developer.new(name: 1337, salary: "50000")
+
+ assert_equal "50000", developer.salary_before_type_cast
+ assert_equal 1337, developer.name_before_type_cast
+
+ assert_equal 50000, developer.salary
+ assert_equal "1337", developer.name
+
+ developer.save!
+
+ assert_equal "50000", developer.salary_before_type_cast
+ assert_equal 1337, developer.name_before_type_cast
+
+ assert_equal 50000, developer.salary
+ assert_equal "1337", developer.name
+ end
+
def test_write_nil_to_time_attributes
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -728,19 +746,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert "unknown attribute: hello", error.message
end
- def test_read_attribute_overwrites_private_method_not_considered_implemented
- # simulate a model with a db column that shares its name an inherited
- # private method (e.g. Object#system)
- #
- Object.class_eval do
- private
- def title; "private!"; end
+ def test_methods_override_in_multi_level_subclass
+ klass = Class.new(Developer) do
+ def name
+ "dev:#{read_attribute(:name)}"
+ end
+ end
+
+ 2.times { klass = Class.new klass }
+ dev = klass.new(name: 'arthurnn')
+ dev.save!
+ assert_equal 'dev:arthurnn', dev.reload.name
+ end
+
+ def test_global_methods_are_overwritten
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'computers'
+ end
+
+ assert !klass.instance_method_already_implemented?(:system)
+ computer = klass.new
+ assert_nil computer.system
+ end
+
+ def test_global_methods_are_overwritte_when_subclassing
+ klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }
+
+ subklass = Class.new(klass) do
+ self.table_name = 'computers'
end
- assert !@target.instance_method_already_implemented?(:title)
- topic = @target.new
- assert_nil topic.title
- Object.send(:undef_method, :title) # remove test method from object
+ assert !klass.instance_method_already_implemented?(:system)
+ assert !subklass.instance_method_already_implemented?(:system)
+ computer = subklass.new
+ assert_nil computer.system
end
def test_instance_method_should_be_defined_on_the_base_class
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 517d2674a7..f7584c3a51 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -17,8 +17,35 @@ require 'models/tag'
require 'models/tagging'
require 'models/treasure'
require 'models/eye'
+require 'models/electron'
+require 'models/molecule'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
+ def test_autosave_validation
+ person = Class.new(ActiveRecord::Base) {
+ self.table_name = 'people'
+ validate :should_be_cool, :on => :create
+ def self.name; 'Person'; end
+
+ private
+
+ def should_be_cool
+ unless self.first_name == 'cool'
+ errors.add :first_name, "not cool"
+ end
+ end
+ }
+ reference = Class.new(ActiveRecord::Base) {
+ self.table_name = "references"
+ def self.name; 'Reference'; end
+ belongs_to :person, autosave: true, class: person
+ }
+
+ u = person.create!(first_name: 'cool')
+ u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create'
+ assert reference.create!(person: u).persisted?
+ end
+
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
end
@@ -37,10 +64,6 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
private
- def base
- ActiveRecord::Base
- end
-
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
reflection = model.reflect_on_association(association_name)
assert_no_difference "callbacks_for_model(#{model.name}).length" do
@@ -49,9 +72,9 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
end
def callbacks_for_model(model)
- model.instance_variables.grep(/_callbacks$/).map do |ivar|
+ model.instance_variables.grep(/_callbacks$/).flat_map do |ivar|
model.instance_variable_get(ivar)
- end.flatten
+ end
end
end
@@ -343,6 +366,33 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
end
+class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
+ def test_invalid_adding_with_nested_attributes
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+ molecule.save
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
+ end
+
+ def test_valid_adding_with_nested_attributes
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+
+ molecule.electrons = [valid_electron]
+ molecule.save
+
+ assert valid_electron.valid?
+ assert molecule.persisted?
+ assert_equal 1, molecule.electrons.count
+ end
+end
+
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
fixtures :companies, :people
@@ -401,7 +451,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
assert !new_client.persisted?
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm(true).size
end
def test_adding_before_save
@@ -455,7 +505,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 2, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm(true).size
end
def test_build_many_before_save
@@ -464,7 +514,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm(true).size
end
def test_build_via_block_before_save
@@ -475,7 +525,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 2, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm(true).size
end
def test_build_many_via_block_before_save
@@ -488,7 +538,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm(true).size
end
def test_replace_on_new_object
@@ -568,12 +618,19 @@ end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- def setup
- super
+ setup do
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
+ teardown do
+ # We are running without transactional fixtures and need to cleanup.
+ Bird.delete_all
+ Parrot.delete_all
+ @ship.delete
+ @pirate.delete
+ end
+
# reload
def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
@pirate.mark_for_destruction
@@ -1176,15 +1233,15 @@ module AutosaveAssociationOnACollectionAssociationTests
end
def test_should_default_invalid_error_from_i18n
- I18n.backend.store_translations(:en, :activerecord => {:errors => { :models =>
- { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
+ I18n.backend.store_translations(:en, activerecord: {errors: { models:
+ { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } }
}})
- @pirate.send(@association_name).build(:name => '')
+ @pirate.send(@association_name).build(name: '')
assert !@pirate.valid?
assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
- assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages
+ assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages
assert @pirate.errors[@association_name].empty?
ensure
I18n.backend = I18n::Backend::Simple.new
@@ -1300,6 +1357,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
def setup
super
@association_name = :birds
+ @associated_model_name = :bird
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@child_1 = @pirate.birds.create(:name => 'Posideons Killer')
@@ -1314,12 +1372,30 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T
def setup
super
+ @association_name = :autosaved_parrots
+ @associated_model_name = :parrot
+ @habtm = true
+
+ @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
+ @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
+ end
+
+ include AutosaveAssociationOnACollectionAssociationTests
+end
+
+class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false unless supports_savepoints?
+
+ def setup
+ super
@association_name = :parrots
+ @associated_model_name = :parrot
@habtm = true
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
- @child_1 = @pirate.parrots.create(:name => 'Posideons Killer')
- @child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne')
+ @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
+ @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
end
include AutosaveAssociationOnACollectionAssociationTests
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4bc6002bfe..2e5b8cffa6 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -212,7 +212,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56?
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -321,7 +321,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_load
topics = Topic.all.merge!(:order => 'id').to_a
- assert_equal(4, topics.size)
+ assert_equal(5, topics.size)
assert_equal(topics(:first).title, topics.first.title)
end
@@ -533,6 +533,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_equality_of_new_records
assert_not_equal Topic.new, Topic.new
+ assert_equal false, Topic.new == Topic.new
end
def test_equality_of_destroyed_records
@@ -544,6 +545,47 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal topic_2, topic_1
end
+ def test_equality_with_blank_ids
+ one = Subscriber.new(:id => '')
+ two = Subscriber.new(:id => '')
+ assert_equal one, two
+ end
+
+ def test_equality_of_relation_and_collection_proxy
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation'
+ assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy'
+ end
+
+ def test_equality_of_relation_and_array
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array'
+ end
+
+ def test_equality_of_relation_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation'
+ end
+
+ def test_equality_of_collection_proxy_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy'
+ end
+
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
@@ -578,12 +620,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal nil, Topic.find_by_id(topic.id)
end
- def test_blank_ids
- one = Subscriber.new(:id => '')
- two = Subscriber.new(:id => '')
- assert_equal one, two
- end
-
def test_comparison_with_different_objects
topic = Topic.create
category = Category.create(:name => "comparison")
@@ -626,6 +662,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq
ensure
silence_warnings { Encoding.default_internal = old_default_internal }
+ Weird.reset_column_information
end
end
@@ -1129,7 +1166,7 @@ class BasicsTest < ActiveRecord::TestCase
k = Class.new(ak)
k.table_name = "projects"
orig_name = k.sequence_name
- return skip "sequences not supported by db" unless orig_name
+ skip "sequences not supported by db" unless orig_name
assert_equal k.reset_sequence_name, orig_name
end
@@ -1379,6 +1416,8 @@ class BasicsTest < ActiveRecord::TestCase
})
rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
ActiveRecord::Base.connection_handler.clear_all_connections!
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 38c2560d69..c12fa03015 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -35,6 +35,14 @@ class EachTest < ActiveRecord::TestCase
end
end
+ if Enumerator.method_defined? :size
+ def test_each_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_each(:batch_size => 1).size
+ assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size
+ assert_equal 11, Post.find_each(:batch_size => 10_000).size
+ end
+ end
+
def test_each_enumerator_should_execute_one_query_per_batch
assert_queries(@total + 1) do
Post.find_each(:batch_size => 1).with_index do |post, index|
@@ -46,7 +54,9 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_raise_if_select_is_set_without_id
assert_raise(RuntimeError) do
- Post.select(:title).find_each(:batch_size => 1) { |post| post }
+ Post.select(:title).find_each(batch_size: 1) { |post|
+ flunk "should not call this block"
+ }
end
end
@@ -151,6 +161,12 @@ class EachTest < ActiveRecord::TestCase
assert_equal special_posts_ids, posts.map(&:id)
end
+ def test_find_in_batches_should_not_modify_passed_options
+ assert_nothing_raised do
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
+ end
+ end
+
def test_find_in_batches_should_use_any_column_as_primary_key
nick_order_subscribers = Subscriber.order('nick asc')
start_nick = nick_order_subscribers.second.nick
@@ -170,4 +186,27 @@ class EachTest < ActiveRecord::TestCase
end
end
end
+
+ def test_find_in_batches_should_return_an_enumerator
+ enum = nil
+ assert_queries(0) do
+ enum = Post.find_in_batches(:batch_size => 1)
+ end
+ assert_queries(4) do
+ enum.first(4) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
+
+ if Enumerator.method_defined? :size
+ def test_find_in_batches_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_in_batches(:batch_size => 1).size
+ assert_equal 6, Post.find_in_batches(:batch_size => 2).size
+ assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size
+ assert_equal 4, Post.find_in_batches(:batch_size => 3).size
+ assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
+ end
+ end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 291751c435..40f73cd68c 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -20,13 +20,13 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
- @listener = LogListener.new
+ @subscriber = LogListener.new
@pk = Topic.columns.find { |c| c.primary }
- ActiveSupport::Notifications.subscribe('sql.active_record', @listener)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
- def teardown
- ActiveSupport::Notifications.unsubscribe(@listener)
+ teardown do
+ ActiveSupport::Notifications.unsubscribe(@subscription)
end
if ActiveRecord::Base.connection.supports_statement_cache?
@@ -37,7 +37,7 @@ module ActiveRecord
@connection.exec_query(sql, 'SQL', binds)
- message = @listener.calls.find { |args| args[4][:sql] == sql }
+ message = @subscriber.calls.find { |args| args[4][:sql] == sql }
assert_equal binds, message[4][:binds]
end
@@ -48,14 +48,14 @@ module ActiveRecord
@connection.exec_query(sql, 'SQL', binds)
- message = @listener.calls.find { |args| args[4][:sql] == sql }
+ message = @subscriber.calls.find { |args| args[4][:sql] == sql }
assert_equal [[@pk, 3]], message[4][:binds]
end
def test_find_one_uses_binds
Topic.find(1)
binds = [[@pk, 1]]
- message = @listener.calls.find { |args| args[4][:binds] == binds }
+ message = @subscriber.calls.find { |args| args[4][:binds] == binds }
assert message, 'expected a message with binds'
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2f6913167d..b8de78934e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -278,7 +278,7 @@ class CalculationsTest < ActiveRecord::TestCase
c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
- assert_equal 4, c['CLIENT']
+ assert_equal 5, c['CLIENT']
assert_equal 2, c['FIRM']
end
@@ -286,7 +286,7 @@ class CalculationsTest < ActiveRecord::TestCase
c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
- assert_equal 4, c['CLIENT']
+ assert_equal 5, c['CLIENT']
assert_equal 2, c['FIRM']
end
@@ -387,6 +387,20 @@ class CalculationsTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Account.count(1, 2, 3) }
end
+ def test_count_with_order
+ assert_equal 6, Account.order(:credit_limit).count
+ end
+
+ def test_count_with_reverse_order
+ assert_equal 6, Account.order(:credit_limit).reverse_order.count
+ end
+
+ def test_count_with_where_and_order
+ assert_equal 1, Account.where(firm_name: '37signals').count
+ assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count
+ assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count
+ end
+
def test_should_sum_expression
# Oracle adapter returns floating point value 636.0 after SUM
if current_adapter?(:OracleAdapter)
@@ -466,14 +480,14 @@ class CalculationsTest < ActiveRecord::TestCase
def test_distinct_is_honored_when_used_with_count_operation_after_group
# Count the number of authors for approved topics
approved_topics_count = Topic.group(:approved).count(:author_name)[true]
- assert_equal approved_topics_count, 3
+ assert_equal approved_topics_count, 4
# Count the number of distinct authors for approved Topics
distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true]
- assert_equal distinct_authors_for_approved_count, 2
+ assert_equal distinct_authors_for_approved_count, 3
end
def test_pluck
- assert_equal [1,2,3,4], Topic.order(:id).pluck(:id)
+ assert_equal [1,2,3,4,5], Topic.order(:id).pluck(:id)
end
def test_pluck_without_column_names
@@ -509,7 +523,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_with_qualified_column_name
- assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id")
+ assert_equal [1,2,3,4,5], Topic.order(:id).pluck("topics.id")
end
def test_pluck_auto_table_name_prefix
@@ -557,11 +571,13 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_multiple_columns
assert_equal [
[1, "The First Topic"], [2, "The Second Topic of the day"],
- [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"]
+ [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"],
+ [5, "The Fifth Topic of the day"]
], Topic.order(:id).pluck(:id, :title)
assert_equal [
[1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
- [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"]
+ [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"],
+ [5, "The Fifth Topic of the day", "Jason"]
], Topic.order(:id).pluck(:id, :title, :author_name)
end
@@ -587,7 +603,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_replaces_select_clause
taks_relation = Topic.select(:approved, :id).order(:id)
- assert_equal [1,2,3,4], taks_relation.pluck(:id)
- assert_equal [false, true, true, true], taks_relation.pluck(:approved)
+ assert_equal [1,2,3,4,5], taks_relation.pluck(:id)
+ assert_equal [false, true, true, true, true], taks_relation.pluck(:approved)
end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index dbb2f223cd..c1dd1f1c69 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -82,7 +82,7 @@ module ActiveRecord
assert_equal "", not_null_text_column.default
end
- def test_has_default_should_return_false_for_blog_and_test_data_types
+ def test_has_default_should_return_false_for_blob_and_text_data_types
blob_column = MysqlAdapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
@@ -116,7 +116,7 @@ module ActiveRecord
assert_equal "", not_null_text_column.default
end
- def test_has_default_should_return_false_for_blog_and_test_data_types
+ def test_has_default_should_return_false_for_blob_and_text_data_types
blob_column = Mysql2Adapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
@@ -127,13 +127,13 @@ module ActiveRecord
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Identity.new
+ oid = PostgreSQLAdapter::OID::Integer.new
bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
assert_equal :integer, bigint_column.type
end
def test_smallint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Identity.new
+ oid = PostgreSQLAdapter::OID::Integer.new
smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
deleted file mode 100644
index eb2fe5639b..0000000000
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module ConnectionAdapters
- class ConnectionPool
- def insert_connection_for_test!(c)
- synchronize do
- @connections << c
- @available.add c
- end
- end
- end
-
- class AbstractAdapterTest < ActiveRecord::TestCase
- attr_reader :adapter
-
- def setup
- @adapter = AbstractAdapter.new nil, nil
- end
-
- def test_in_use?
- assert_not adapter.in_use?, 'adapter is not in use'
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- end
-
- def test_lease_twice
- assert adapter.lease, 'should lease adapter'
- assert_not adapter.lease, 'should not lease adapter'
- end
-
- def test_last_use
- assert_not adapter.last_use
- adapter.lease
- assert adapter.last_use
- end
-
- def test_expire_mutates_in_use
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- adapter.expire
- assert_not adapter.in_use?, 'adapter is in use'
- end
-
- def test_close
- pool = ConnectionPool.new(ConnectionSpecification.new({}, nil))
- pool.insert_connection_for_test! adapter
- adapter.pool = pool
-
- # Make sure the pool marks the connection in use
- assert_equal adapter, pool.connection
- assert adapter.in_use?
-
- # Close should put the adapter back in the pool
- adapter.close
- assert_not adapter.in_use?
-
- assert_equal adapter, pool.connection
- end
- end
- end
-end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
new file mode 100644
index 0000000000..662e19f35e
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AdapterLeasingTest < ActiveRecord::TestCase
+ class Pool < ConnectionPool
+ def insert_connection_for_test!(c)
+ synchronize do
+ @connections << c
+ @available.add c
+ end
+ end
+ end
+
+ def setup
+ @adapter = AbstractAdapter.new nil, nil
+ end
+
+ def test_in_use?
+ assert_not @adapter.in_use?, 'adapter is not in use'
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_lease_twice
+ assert @adapter.lease, 'should lease adapter'
+ assert_not @adapter.lease, 'should not lease adapter'
+ end
+
+ def test_expire_mutates_in_use
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ @adapter.expire
+ assert_not @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_close
+ pool = Pool.new(ConnectionSpecification.new({}, nil))
+ pool.insert_connection_for_test! @adapter
+ @adapter.pool = pool
+
+ # Make sure the pool marks the connection in use
+ assert_equal @adapter, pool.connection
+ assert @adapter.in_use?
+
+ # Close should put the adapter back in the pool
+ @adapter.close
+ assert_not @adapter.in_use?
+
+ assert_equal @adapter, pool.connection
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 318cc5a32c..3e33b30144 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,133 +2,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
-
- class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
-
- def klass
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig
- end
-
- def setup
- @previous_database_url = ENV.delete("DATABASE_URL")
- end
-
- def teardown
- ENV["DATABASE_URL"] = @previous_database_url
- end
-
- def test_string_connection
- config = { "production" => "postgres://localhost/foo" }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_url_sub_key
- config = { "production" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_hash
- config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank
- config = {}
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {}
- actual = klass.new(config).resolve
- expected = { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost" }
- assert_equal expected, actual["production"]
- assert_equal expected, actual["development"]
- assert_equal expected, actual["test"]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
- end
-
- def test_sting_with_database_url
- ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
-
- config = { "production" => "postgres://localhost/foo" }
- actual = klass.new(config).resolve
-
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_url_sub_key_with_database_url
- ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
-
- config = { "production" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_no_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"production" => { "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
- end
-
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
@klass = Class.new(Base) { def self.name; 'klass'; 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
new file mode 100644
index 0000000000..da852aaa02
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -0,0 +1,192 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
+ def setup
+ @previous_database_url = ENV.delete("DATABASE_URL")
+ end
+
+ teardown do
+ ENV["DATABASE_URL"] = @previous_database_url
+ end
+
+ def resolve_config(config)
+ ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ end
+
+ def resolve_spec(spec, config)
+ ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ end
+
+ def test_resolver_with_database_uri_and_current_env_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = 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
+ 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_known_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:production, config)
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_unknown_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_raises AdapterNotSpecified do
+ resolve_spec(:production, config)
+ 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" } }
+ actual = resolve_spec("postgres://localhost/foo", config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_jdbc_url
+ config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_environment_does_not_exist_in_config_url_does_exist
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve_config(config)
+ expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expect_prod, actual["default_env"]
+ end
+
+ def test_url_with_hyphenated_scheme
+ ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
+ config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:default_env, config)
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_string_connection
+ config = { "default_env" => "postgres://localhost/foo" }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_url_sub_key
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_hash
+ config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank
+ config = {}
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {}
+ actual = resolve_config(config)
+ expected = { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost" }
+ assert_equal expected, actual["default_env"]
+ assert_equal nil, actual["production"]
+ assert_equal nil, actual["development"]
+ assert_equal nil, actual["test"]
+ assert_equal nil, actual[:production]
+ assert_equal nil, actual[:development]
+ assert_equal nil, actual[:test]
+ end
+
+ def test_url_sub_key_with_database_url
+ ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
+
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 00667cc52e..77d9ae9b8e 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -31,6 +31,8 @@ module ActiveRecord
object_id = ActiveRecord::Base.connection.object_id
rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
pid = fork {
rd.close
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 2da51ea015..8d15a76735 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/concurrency/latch'
module ActiveRecord
module ConnectionAdapters
@@ -22,8 +23,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
@pool.disconnect!
end
@@ -89,10 +89,9 @@ module ActiveRecord
end
def test_full_pool_exception
+ @pool.size.times { @pool.checkout }
assert_raises(ConnectionTimeoutError) do
- (@pool.size + 1).times do
- @pool.checkout
- end
+ @pool.checkout
end
end
@@ -125,7 +124,6 @@ module ActiveRecord
@pool.checkout
@pool.checkout
@pool.checkout
- @pool.dead_connection_timeout = 0
connections = @pool.connections.dup
@@ -135,21 +133,25 @@ module ActiveRecord
end
def test_reap_inactive
+ ready = ActiveSupport::Concurrency::Latch.new
@pool.checkout
- @pool.checkout
- @pool.checkout
- @pool.dead_connection_timeout = 0
-
- connections = @pool.connections.dup
- connections.each do |conn|
- conn.extend(Module.new { def active?; false; end; })
+ child = Thread.new do
+ @pool.checkout
+ @pool.checkout
+ ready.release
+ Thread.stop
end
+ ready.await
+
+ assert_equal 3, active_connections(@pool).size
+ child.terminate
+ child.join
@pool.reap
- assert_equal 0, @pool.connections.length
+ assert_equal 1, active_connections(@pool).size
ensure
- connections.each(&:close)
+ @pool.connections.each(&:close)
end
def test_remove_connection
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index fdd1914cba..3c2f5d4219 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -82,15 +82,34 @@ module ActiveRecord
assert_equal password, spec["password"]
end
- def test_url_host_db_for_sqlite3
- spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test'
+ def test_url_with_authority_for_sqlite3
+ spec = resolve 'sqlite3:///foo_test'
assert_equal('/foo_test', spec["database"])
end
- def test_url_host_memory_db_for_sqlite3
- spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:'
+ def test_url_absolute_path_for_sqlite3
+ spec = resolve 'sqlite3:/foo_test'
+ assert_equal('/foo_test', spec["database"])
+ end
+
+ def test_url_relative_path_for_sqlite3
+ spec = resolve 'sqlite3:foo_test'
+ assert_equal('foo_test', spec["database"])
+ end
+
+ def test_url_memory_db_for_sqlite3
+ spec = resolve 'sqlite3::memory:'
assert_equal(':memory:', spec["database"])
end
+
+ def test_url_sub_key_for_sqlite3
+ spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'}
+ assert_equal({
+ "adapter" => "sqlite3",
+ "database" => "foo",
+ "encoding" => "utf8" }, spec)
+ end
+
end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 7e3d91e08c..7d438803a1 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -206,7 +206,7 @@ if current_adapter?(:PostgreSQLAdapter)
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 teardown
+ teardown do
@connection.schema_search_path = @old_search_path
Default.reset_column_information
end
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
index 9e268dad74..94447addc1 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -10,7 +10,7 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
@connection = ActiveRecord::Base.connection
end
- def teardown
+ teardown do
return if in_memory_db?
spec = ActiveRecord::Base.connection_config
ActiveRecord::Base.establish_connection(spec)
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 1f98801e93..3b2f0dfe07 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -51,6 +51,77 @@ class EnumTest < ActiveRecord::TestCase
assert @book.written?
end
+ test "enum changed attributes" do
+ old_status = @book.status
+ @book.status = :published
+ assert_equal old_status, @book.changed_attributes[:status]
+ end
+
+ test "enum changes" do
+ old_status = @book.status
+ @book.status = :published
+ assert_equal [old_status, 'published'], @book.changes[:status]
+ end
+
+ test "enum attribute was" do
+ old_status = @book.status
+ @book.status = :published
+ assert_equal old_status, @book.attribute_was(:status)
+ end
+
+ test "enum attribute changed" do
+ @book.status = :published
+ assert @book.attribute_changed?(:status)
+ end
+
+ test "enum attribute changed to" do
+ @book.status = :published
+ assert @book.attribute_changed?(:status, to: 'published')
+ end
+
+ test "enum attribute changed from" do
+ old_status = @book.status
+ @book.status = :published
+ assert @book.attribute_changed?(:status, from: old_status)
+ end
+
+ test "enum attribute changed from old status to new status" do
+ old_status = @book.status
+ @book.status = :published
+ assert @book.attribute_changed?(:status, from: old_status, to: 'published')
+ end
+
+ test "enum didn't change" do
+ old_status = @book.status
+ @book.status = old_status
+ assert_not @book.attribute_changed?(:status)
+ end
+
+ test "persist changes that are dirty" do
+ @book.status = :published
+ assert @book.attribute_changed?(:status)
+ @book.status = :written
+ assert @book.attribute_changed?(:status)
+ end
+
+ test "reverted changes that are not dirty" do
+ old_status = @book.status
+ @book.status = :published
+ assert @book.attribute_changed?(:status)
+ @book.status = old_status
+ assert_not @book.attribute_changed?(:status)
+ end
+
+ test "reverted changes are not dirty going from nil to value and back" do
+ book = Book.create!(nullable_status: nil)
+
+ book.nullable_status = :married
+ assert book.attribute_changed?(:nullable_status)
+
+ book.nullable_status = nil
+ assert_not book.attribute_changed?(:nullable_status)
+ end
+
test "assign non existing value raises an error" do
e = assert_raises(ArgumentError) do
@book.status = :unknown
@@ -92,4 +163,127 @@ class EnumTest < ActiveRecord::TestCase
test "_before_type_cast returns the enum label (required for form fields)" do
assert_equal "proposed", @book.status_before_type_cast
end
+
+ test "reserved enum names" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written, :published]
+ end
+
+ conflicts = [
+ :column, # generates class method .columns, which conflicts with an AR method
+ :logger, # generates #logger, which conflicts with an AR method
+ :attributes, # generates #attributes=, which conflicts with an AR method
+ ]
+
+ conflicts.each_with_index do |name, i|
+ assert_raises(ArgumentError, "enum name `#{name}` should not be allowed") do
+ klass.class_eval { enum name => ["value_#{i}"] }
+ end
+ end
+ end
+
+ test "reserved enum values" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written, :published]
+ end
+
+ conflicts = [
+ :new, # generates a scope that conflicts with an AR class method
+ :valid, # generates #valid?, which conflicts with an AR method
+ :save, # generates #save!, which conflicts with an AR method
+ :proposed, # same value as an existing enum
+ :public, :private, :protected, # generates a method that conflict with ruby words
+ ]
+
+ conflicts.each_with_index do |value, i|
+ assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
+ klass.class_eval { enum "status_#{i}" => [value] }
+ end
+ end
+ end
+
+ test "overriding enum method should not raise" do
+ assert_nothing_raised do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+
+ def published!
+ super
+ "do publish work..."
+ end
+
+ enum status: [:proposed, :written, :published]
+
+ def written!
+ super
+ "do written work..."
+ end
+ end
+ end
+ end
+
+ test "validate uniqueness" do
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Book'; end
+ enum status: [:proposed, :written]
+ validates_uniqueness_of :status
+ end
+ klass.delete_all
+ klass.create!(status: "proposed")
+ book = klass.new(status: "written")
+ assert book.valid?
+ book.status = "proposed"
+ assert_not book.valid?
+ end
+
+ test "validate inclusion of value in array" do
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Book'; end
+ enum status: [:proposed, :written]
+ validates_inclusion_of :status, in: ["written"]
+ end
+ klass.delete_all
+ invalid_book = klass.new(status: "proposed")
+ assert_not invalid_book.valid?
+ valid_book = klass.new(status: "written")
+ assert valid_book.valid?
+ end
+
+ test "enums are distinct per class" do
+ klass1 = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written]
+ end
+
+ klass2 = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:drafted, :uploaded]
+ end
+
+ book1 = klass1.proposed.create!
+ book1.status = :written
+ assert_equal ['proposed', 'written'], book1.status_change
+
+ book2 = klass2.drafted.create!
+ book2.status = :uploaded
+ assert_equal ['drafted', 'uploaded'], book2.status_change
+ end
+
+ test "enums are inheritable" do
+ subklass1 = Class.new(Book)
+
+ subklass2 = Class.new(Book) do
+ enum status: [:drafted, :uploaded]
+ end
+
+ book1 = subklass1.proposed.create!
+ book1.status = :written
+ assert_equal ['proposed', 'written'], book1.status_change
+
+ book2 = subklass2.drafted.create!
+ book2.status = :uploaded
+ assert_equal ['drafted', 'uploaded'], book2.status_change
+ end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index b00e2744b9..8de2ddb10d 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -48,7 +48,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert queries.empty?
end
- def teardown
+ teardown do
ActiveRecord::ExplainRegistry.reset
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 6dac5db111..9d25bdd82a 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -26,8 +26,12 @@ if ActiveRecord::Base.connection.supports_explain?
sql, binds = queries[0]
assert_match "SELECT", sql
- assert_match "honda", sql
- assert_equal [], binds
+ if binds.any?
+ assert_equal 1, binds.length
+ assert_equal "honda", binds.flatten.last
+ else
+ assert_match 'honda', sql
+ end
end
def test_exec_explain_with_no_binds
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 3ff22f222f..6ab2657c44 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -5,6 +5,11 @@ class FinderRespondToTest < ActiveRecord::TestCase
fixtures :topics
+ def test_should_preserve_normal_respond_to_behaviour_on_base
+ assert_respond_to ActiveRecord::Base, :new
+ assert !ActiveRecord::Base.respond_to?(:find_by_something)
+ end
+
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { }
assert_respond_to Topic, :method_added_for_finder_respond_to_test
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 5125d5df2a..c0440744e9 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -33,6 +33,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ def test_find_passing_active_record_object_is_deprecated
+ assert_deprecated do
+ Topic.find(Topic.last)
+ end
+ end
+
def test_symbols_table_ref
Post.first # warm up
x = Symbol.all_symbols.count
@@ -56,17 +62,33 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
- assert_equal false, Topic.exists?(Topic.new)
+ assert_equal false, Topic.exists?(Topic.new.id)
- begin
- assert_equal false, Topic.exists?("foo")
- rescue ActiveRecord::StatementInvalid
- # PostgreSQL complains about string comparison with integer field
- rescue Exception
- flunk
+ assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ end
+
+ def test_exists_passing_active_record_object_is_deprecated
+ assert_deprecated do
+ Topic.exists?(Topic.new)
end
+ end
- assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ def test_exists_fails_when_parameter_has_invalid_type
+ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter)
+ assert_raises ActiveRecord::StatementInvalid do
+ Topic.exists?(("9"*53).to_i) # number that's bigger than int
+ end
+ else
+ assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_raises ActiveRecord::StatementInvalid do
+ Topic.exists?("foo")
+ end
+ else
+ assert_equal false, Topic.exists?("foo")
+ end
end
def test_exists_does_not_select_columns_without_alias
@@ -254,6 +276,94 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_second
+ assert_equal topics(:second).title, Topic.second.title
+ end
+
+ def test_second_with_offset
+ assert_equal topics(:fifth), Topic.offset(3).second
+ end
+
+ def test_second_have_primary_key_order_by_default
+ expected = topics(:second)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.second
+ end
+
+ def test_model_class_responds_to_second_bang
+ assert Topic.second!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.second!
+ end
+ end
+
+ def test_third
+ assert_equal topics(:third).title, Topic.third.title
+ end
+
+ def test_third_with_offset
+ assert_equal topics(:fifth), Topic.offset(2).third
+ end
+
+ def test_third_have_primary_key_order_by_default
+ expected = topics(:third)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.third
+ end
+
+ def test_model_class_responds_to_third_bang
+ assert Topic.third!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.third!
+ end
+ end
+
+ def test_fourth
+ assert_equal topics(:fourth).title, Topic.fourth.title
+ end
+
+ def test_fourth_with_offset
+ assert_equal topics(:fifth), Topic.offset(1).fourth
+ end
+
+ def test_fourth_have_primary_key_order_by_default
+ expected = topics(:fourth)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.fourth
+ end
+
+ def test_model_class_responds_to_fourth_bang
+ assert Topic.fourth!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.fourth!
+ end
+ end
+
+ def test_fifth
+ assert_equal topics(:fifth).title, Topic.fifth.title
+ end
+
+ def test_fifth_with_offset
+ assert_equal topics(:fifth), Topic.offset(0).fifth
+ end
+
+ def test_fifth_have_primary_key_order_by_default
+ expected = topics(:fifth)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.fifth
+ end
+
+ def test_model_class_responds_to_fifth_bang
+ assert Topic.fifth!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.fifth!
+ end
+ end
+
def test_last_bang_present
assert_nothing_raised do
assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
@@ -267,7 +377,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_model_class_responds_to_last_bang
- assert_equal topics(:fourth), Topic.last!
+ assert_equal topics(:fifth), Topic.last!
assert_raises ActiveRecord::RecordNotFound do
Topic.delete_all
Topic.last!
@@ -812,8 +922,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_select_values
- assert_equal ["1","2","3","4","5","6","7","8","9", "10"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
- assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
+ 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 ["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
def test_select_rows
@@ -863,14 +973,23 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_one_message_with_custom_primary_key
- Toy.primary_key = :name
- begin
- Toy.find 'Hello World!'
- rescue ActiveRecord::RecordNotFound => e
- assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
+ table_with_custom_primary_key do |model|
+ model.primary_key = :name
+ e = assert_raises(ActiveRecord::RecordNotFound) do
+ model.find 'Hello World!'
+ end
+ assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message
+ end
+ end
+
+ def test_find_some_message_with_custom_primary_key
+ table_with_custom_primary_key do |model|
+ model.primary_key = :name
+ e = assert_raises(ActiveRecord::RecordNotFound) do
+ model.find 'Hello', 'World!'
+ end
+ assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message
end
- ensure
- Toy.reset_primary_key
end
def test_find_without_primary_key
@@ -892,10 +1011,11 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ def table_with_custom_primary_key
+ yield(Class.new(Toy) do
+ def self.name
+ 'MercedesCar'
+ end
+ end)
end
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f3a4887a85..8bbc0af758 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -254,7 +254,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_fixtures_are_set_up_with_database_env_variable
db_url_tmp = ENV['DATABASE_URL']
- ENV['DATABASE_URL'] = "sqlite3:///:memory:"
+ ENV['DATABASE_URL'] = "sqlite3::memory:"
ActiveRecord::Base.stubs(:configurations).returns({})
test_case = Class.new(ActiveRecord::TestCase) do
fixtures :accounts
@@ -628,7 +628,9 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
self.class.fixture_path = FIXTURES_ROOT + "/all"
self.class.fixtures :all
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ if File.symlink? FIXTURES_ROOT + "/all/admin"
+ assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ end
ensure
ActiveRecord::FixtureSet.reset_cache
end
@@ -639,7 +641,9 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all')
self.class.fixtures :all
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ if File.symlink? FIXTURES_ROOT + "/all/admin"
+ assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ end
ensure
ActiveRecord::FixtureSet.reset_cache
end
@@ -673,6 +677,12 @@ end
class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
+ require 'models/uuid_parent'
+ require 'models/uuid_child'
+ fixtures :uuid_parents, :uuid_children
+ end
+
def test_identifies_strings
assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo"))
assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO"))
@@ -685,6 +695,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase
def test_identifies_consistently
assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby)
assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2)
+
+ assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid)
+ assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
@@ -778,6 +791,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("frederick", parrots(:frederick).name)
end
+ def test_supports_label_string_interpolation
+ assert_equal("X marks the spot!", pirates(:mark).catchphrase)
+ end
+
def test_supports_polymorphic_belongs_to
assert_equal(pirates(:redbeard), treasures(:sapphire).looter)
assert_equal(parrots(:louis), treasures(:ruby).looter)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 3758224b0c..eaf2cada9d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -41,6 +41,11 @@ def in_memory_db?
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
+def mysql_56?
+ current_adapter?(:Mysql2Adapter) &&
+ ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
+end
+
def supports_savepoints?
ActiveRecord::Base.connection.supports_savepoints?
end
@@ -106,6 +111,15 @@ def verify_default_timezone_config
end
end
+def enable_uuid_ossp!(connection)
+ return false unless connection.supports_extensions?
+ return true if connection.extension_enabled?('uuid-ossp')
+
+ connection.enable_extension 'uuid-ossp'
+ connection.commit_db_transaction
+ connection.reconnect!
+end
+
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
@@ -163,7 +177,7 @@ class SQLSubscriber
def start(name, id, payload)
@payloads << payload
- @logged << [payload[:sql], payload[:name], payload[:binds]]
+ @logged << [payload[:sql].squish, payload[:name], payload[:binds]]
end
def finish(name, id, payload); end
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 367d04a154..b4617cf6f9 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -15,7 +15,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase
end
teardown do
- @klass.connection.drop_table :hot_compatibilities
+ ActiveRecord::Base.connection.drop_table :hot_compatibilities
end
test "insert after remove_column" do
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index d2b5a06b55..f5f85f2412 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -222,9 +222,9 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_inheritance_condition
- assert_equal 10, Company.count
+ assert_equal 11, Company.count
assert_equal 2, Firm.count
- assert_equal 4, Client.count
+ assert_equal 5, Client.count
end
def test_alt_inheritance_condition
@@ -339,7 +339,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
ActiveSupport::Dependencies.log_activity = true
end
- def teardown
+ teardown do
ActiveSupport::Dependencies.log_activity = false
self.class.const_remove :FirmOnTheFly rescue nil
Firm.const_remove :FirmOnTheFly rescue nil
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 2e71b1a40d..dfb8a608cb 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -3,12 +3,11 @@
require 'cases/helper'
require 'models/company'
require 'models/developer'
-require 'models/car'
-require 'models/bulb'
require 'models/owner'
+require 'models/pet'
class IntegrationTest < ActiveRecord::TestCase
- fixtures :companies, :developers, :owners
+ fixtures :companies, :developers, :owners, :pets
def test_to_param_should_return_string
assert_kind_of String, Client.first.to_param
@@ -91,13 +90,14 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_changes_when_child_touched
- car = Car.create
- Bulb.create(car: car)
+ owner = owners(:blackbeard)
+ pet = pets(:parrot)
+
+ owner.update_column :updated_at, Time.current
+ key = owner.cache_key
- key = car.cache_key
- car.bulb.touch
- car.reload
- assert_not_equal key, car.cache_key
+ assert pet.touch
+ assert_not_equal key, owner.reload.cache_key
end
def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index f6774d7ef4..8416c81f45 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -12,7 +12,7 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist'
end
- def teardown
+ teardown do
Bird.remove_connection
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 428145d00b..285172d33e 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -106,7 +106,23 @@ module ActiveRecord
end
end
- def teardown
+ class RevertNamedIndexMigration1 < SilentMigration
+ def change
+ create_table("horses") do |t|
+ t.column :content, :string
+ t.column :remind_at, :datetime
+ end
+ add_index :horses, :content
+ end
+ end
+
+ class RevertNamedIndexMigration2 < SilentMigration
+ def change
+ add_index :horses, :content, name: "horses_index_named"
+ end
+ end
+
+ teardown do
%w[horses new_horses].each do |table|
if ActiveRecord::Base.connection.table_exists?(table)
ActiveRecord::Base.connection.drop_table(table)
@@ -255,5 +271,20 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
+ # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
+ unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :OracleAdapter)
+ def test_migrate_revert_add_index_with_name
+ RevertNamedIndexMigration1.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:down)
+
+ connection = ActiveRecord::Base.connection
+ assert connection.index_exists?(:horses, :content),
+ "index on content should exist"
+ assert !connection.index_exists?(:horses, :content, name: "horses_index_named"),
+ "horses_index_named index should not exist"
+ end
+ end
+
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index a16ed963fe..c373dc1511 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -431,6 +431,17 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
assert_equal old, person.reload.first_name
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_lock_sending_custom_lock_statement
+ Person.transaction do
+ person = Person.find(1)
+ assert_sql(/LIMIT 1 FOR SHARE NOWAIT/) do
+ person.lock!('FOR SHARE NOWAIT')
+ end
+ end
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_no_locks_no_wait
first, second = duel { Person.find 1 }
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 294f2eb9fe..5418d913b0 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -11,8 +11,7 @@ module ActiveRecord
@table_name = :testings
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index c1d7cd5874..a6d506b04a 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
@connection = Minitest::Mock.new
end
- def teardown
+ teardown do
assert @connection.verify
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index ccf19fb4d0..6a02873cba 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -35,6 +35,14 @@ module ActiveRecord
assert_no_column TestModel, :last_name
end
+ def test_add_column_without_limit
+ # TODO: limit: nil should work with all adapters.
+ skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ add_column :test_models, :description, :string, limit: nil
+ TestModel.reset_column_information
+ assert_nil TestModel.columns_hash["description"].limit
+ end
+
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_unabstracted_database_dependent_types
add_column :test_models, :intelligence_quotient, :tinyint
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 87e29e41ba..77a752f050 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -18,8 +18,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 35b656ee43..a925cf4c05 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -174,13 +174,13 @@ module ActiveRecord
end
def test_invert_add_index
- remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true]
- assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove
+ remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
+ assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove
end
def test_invert_add_index_with_name
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
- assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove
+ assert_equal [:remove_index, [:table, {name: "new_index"}]], remove
end
def test_invert_add_index_with_no_options
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index efaec0f823..62b60f7f7b 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -10,8 +10,7 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
- def teardown
- super
+ teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
connection.drop_table table_name if connection.tables.include?(table_name)
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 8d1daa0a04..35af11f672 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -21,8 +21,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 97efb94b66..84224e6e4c 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -19,8 +19,7 @@ module ActiveRecord
ActiveRecord::SchemaMigration.delete_all
end
- def teardown
- super
+ teardown do
ActiveRecord::SchemaMigration.drop_table
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 19eb7d3c9e..4485701a4e 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -11,8 +11,7 @@ module ActiveRecord
@table_name = :testings
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 0363bf1048..455ec78f68 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -33,7 +33,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.schema_cache.clear!
end
- def teardown
+ teardown do
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
@@ -328,6 +328,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_proper_table_name_on_migrator
+ reminder_class = new_isolated_reminder_class
assert_deprecated do
assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
end
@@ -335,30 +336,30 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
end
assert_deprecated do
- assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(reminder_class)
end
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_deprecated do
- assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal reminder_class.table_name, ActiveRecord::Migrator.proper_table_name(reminder_class)
end
# Use the model's own prefix/suffix if a model is given
ActiveRecord::Base.table_name_prefix = "ARprefix_"
ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = 'prefix_'
+ reminder_class.table_name_suffix = '_suffix'
+ reminder_class.reset_table_name
assert_deprecated do
- assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
+ assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(reminder_class)
end
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = ''
+ reminder_class.table_name_suffix = ''
+ reminder_class.reset_table_name
# Use AR::Base's prefix/suffix if string or symbol is given
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_deprecated do
assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
end
@@ -368,28 +369,29 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_proper_table_name_on_migration
+ reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
assert_equal "table", migration.proper_table_name('table')
assert_equal "table", migration.proper_table_name(:table)
- assert_equal "reminders", migration.proper_table_name(Reminder)
- Reminder.reset_table_name
- assert_equal Reminder.table_name, migration.proper_table_name(Reminder)
+ assert_equal "reminders", migration.proper_table_name(reminder_class)
+ reminder_class.reset_table_name
+ assert_equal reminder_class.table_name, migration.proper_table_name(reminder_class)
# Use the model's own prefix/suffix if a model is given
ActiveRecord::Base.table_name_prefix = "ARprefix_"
ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- assert_equal "prefix_reminders_suffix", migration.proper_table_name(Reminder)
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
+ reminder_class.table_name_prefix = 'prefix_'
+ reminder_class.table_name_suffix = '_suffix'
+ reminder_class.reset_table_name
+ assert_equal "prefix_reminders_suffix", migration.proper_table_name(reminder_class)
+ reminder_class.table_name_prefix = ''
+ reminder_class.table_name_suffix = ''
+ reminder_class.reset_table_name
# Use AR::Base's prefix/suffix if string or symbol is given
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
+ reminder_class.reset_table_name
assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options)
assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options)
end
@@ -532,11 +534,13 @@ class MigrationTest < ActiveRecord::TestCase
end
protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ # This is needed to isolate class_attribute assignments like `table_name_prefix`
+ # for each test case.
+ def new_isolated_reminder_class
+ Class.new(Reminder) {
+ def self.name; "Reminder"; end
+ def self.base_class; self; end
+ }
end
end
@@ -581,7 +585,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
Person.reset_sequence_name
end
- def teardown
+ teardown do
Person.connection.drop_table(:delete_me) rescue nil
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 3f9854200d..9568aa2217 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -26,8 +26,7 @@ module ActiveRecord
ActiveRecord::SchemaMigration.delete_all rescue nil
end
- def teardown
- super
+ teardown do
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
end
@@ -93,7 +92,7 @@ module ActiveRecord
def test_relative_migrations
list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.migrations("valid/")
+ ActiveRecord::Migrator.migrations("valid")
end
migration_proxy = list.find { |item|
diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb
index ad0d5cce27..7ddb2bfee1 100644
--- a/activerecord/test/cases/mixin_test.rb
+++ b/activerecord/test/cases/mixin_test.rb
@@ -6,10 +6,14 @@ end
class TouchTest < ActiveRecord::TestCase
fixtures :mixins
- def setup
+ setup do
travel_to Time.now
end
+ teardown do
+ travel_back
+ end
+
def test_update
stamped = Mixin.new
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 9124105e6d..f7db195521 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -18,7 +18,7 @@ class ModulesTest < ActiveRecord::TestCase
ActiveRecord::Base.store_full_sti_class = false
end
- def teardown
+ teardown do
# reinstate the constants that we undefined in the setup
@undefined_consts.each do |constant, value|
Object.send :const_set, constant, value unless value.nil?
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 2f89699df7..cf96c3fccf 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -11,24 +11,8 @@ require "models/owner"
require "models/pet"
require 'active_support/hash_with_indifferent_access'
-module AssertRaiseWithMessage
- def assert_raise_with_message(expected_exception, expected_message)
- begin
- error_raised = false
- yield
- rescue expected_exception => error
- error_raised = true
- actual_message = error.message
- end
- assert error_raised
- assert_equal expected_message, actual_message
- end
-end
-
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
- def teardown
+ teardown do
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
@@ -71,9 +55,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_raise_an_ArgumentError_for_non_existing_associations
- assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
+ exception = assert_raise ArgumentError do
Pirate.accepts_nested_attributes_for :honesty
end
+ assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
end
def test_should_disable_allow_destroy_by_default
@@ -213,17 +198,16 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
def setup
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
- assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do
+ exception = assert_raise ArgumentError do
Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
end
+ assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message
end
def test_should_define_an_attribute_writer_method_for_the_association
@@ -275,9 +259,10 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@pirate.ship_attributes = { :id => 1234567890 }
end
+ assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@@ -403,8 +388,6 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
def setup
@ship = Ship.new(:name => 'Nights Dirty Lightning')
@pirate = @ship.build_pirate(:catchphrase => 'Aye')
@@ -460,9 +443,10 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@ship.pirate_attributes = { :id => 1234567890 }
end
+ assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@@ -579,8 +563,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
module NestedAttributesOnACollectionAssociationTests
- include AssertRaiseWithMessage
-
def test_should_define_an_attribute_writer_method_for_the_association
assert_respond_to @pirate, association_setter
end
@@ -670,9 +652,10 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@@ -727,9 +710,10 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
- assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do
+ exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
end
+ assert_equal 'Hash or Array expected, got String ("foo")', exception.message
end
def test_should_work_with_update_as_well
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 6f1e518f45..9209672ac5 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -233,6 +233,22 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nothing_raised { Minimalistic.create!(:id => 2) }
end
+ def test_save_with_duping_of_destroyed_object
+ developer = Developer.create(name: "Kuldeep")
+ developer.destroy
+ new_developer = developer.dup
+ new_developer.save
+ assert new_developer.persisted?
+ end
+
+ def test_dup_of_destroyed_object_is_not_destroyed
+ developer = Developer.create(name: "Kuldeep")
+ developer.destroy
+ new_developer = developer.dup
+ new_developer.save
+ assert_equal new_developer.destroyed?, false
+ end
+
def test_create_many
topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
assert_equal 2, topics.size
@@ -452,7 +468,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_attribute_for_updated_at_on
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_attribute(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
@@ -523,7 +539,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_column_should_not_modify_updated_at
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_column(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
@@ -620,7 +636,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_columns_should_not_modify_updated_at
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_columns(updated_at: prev_month)
assert_equal prev_month, developer.updated_at
@@ -740,7 +756,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") }
ensure
- Reply.reset_callbacks(:validate)
+ Reply.clear_validators!
end
def test_update_attributes!
@@ -761,7 +777,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") }
ensure
- Reply.reset_callbacks(:validate)
+ Reply.clear_validators!
end
def test_destroyed_returns_boolean
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 626c6aeaf8..dd0e934ec2 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -10,7 +10,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
@connection = ActiveRecord::Base.remove_connection
end
- def teardown
+ teardown do
ActiveRecord::Base.clear_all_connections!
ActiveRecord::Base.establish_connection(@connection)
@per_test_teardown.each {|td| td.call }
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 1b915387be..51ddd406ed 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -180,6 +180,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert !col1.equal?(col2)
end
end
+
+ def test_auto_detect_primary_key_from_schema
+ MixedCaseMonkey.reset_primary_key
+ assert_equal "monkeyID", MixedCaseMonkey.primary_key
+ end
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
@@ -214,4 +219,3 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
end
-
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 5566563116..9d89d6a1e8 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -8,7 +8,7 @@ require 'rack'
class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
- def setup
+ teardown do
Task.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache!
end
@@ -118,6 +118,14 @@ class QueryCacheTest < ActiveRecord::TestCase
assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
end
+ def test_cache_passing_a_relation
+ post = Post.first
+ Post.cache do
+ query = post.categories.select(:post_id)
+ assert Post.connection.select_all(query).is_a?(ActiveRecord::Result)
+ end
+ end
+
def test_find_queries
assert_queries(2) { Task.find(1); Task.find(1) }
end
@@ -214,7 +222,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
Post.find(1)
# change the column definition
- Post.connection.change_column :posts, :title, :string, :limit => 80
+ Post.connection.change_column :posts, :title, :string, limit: 80
assert_nothing_raised { Post.find(1) }
# restore the old definition
@@ -241,7 +249,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_update
Task.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
task = Task.find(1)
task.starting = Time.now.utc
@@ -251,7 +258,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_destroy
Task.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
Task.find(1).destroy
end
@@ -259,7 +265,6 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_insert
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
-
Task.cache do
Task.create!
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index e53a27d5dd..f52fd22489 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -10,8 +10,7 @@ module ActiveRecord
@pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
end
- def teardown
- super
+ teardown do
@pool.connections.each(&:close)
end
@@ -64,17 +63,22 @@ module ActiveRecord
spec.config[:reaping_frequency] = 0.0001
pool = ConnectionPool.new spec
- pool.dead_connection_timeout = 0
- conn = pool.checkout
- count = pool.connections.length
+ conn = nil
+ child = Thread.new do
+ conn = pool.checkout
+ Thread.stop
+ end
+ Thread.pass while conn.nil?
+
+ assert conn.in_use?
- conn.extend(Module.new { def active?; false; end; })
+ child.terminate
- while count == pool.connections.length
+ while conn.in_use?
Thread.pass
end
- assert_equal(count - 1, pool.connections.length)
+ assert !conn.in_use?
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index d7ad5ed29f..c085fcf161 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase
def test_column_string_type_and_limit
assert_equal :string, @first.column_for_attribute("title").type
- assert_equal 255, @first.column_for_attribute("title").limit
+ assert_equal 250, @first.column_for_attribute("title").limit
end
def test_column_null_not_null
@@ -87,6 +87,14 @@ class ReflectionTest < ActiveRecord::TestCase
end
end
+ def test_irregular_reflection_class_name
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'plural_irregular', 'plurales_irregulares'
+ end
+ reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
+ assert_equal 'PluralIrregular', reflection.class_name
+ end
+
def test_aggregation_reflection
reflection_for_address = AggregateReflection.new(
:composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
@@ -192,7 +200,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_should_not_raise_error_when_compared_to_other_object
- assert_nothing_raised { Firm.reflections[:clients] == Object.new }
+ assert_nothing_raised { Firm.reflections['clients'] == Object.new }
end
def test_has_many_through_reflection
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 23500bf5d8..ff1c2a0d82 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -17,9 +17,7 @@ class RelationMergingTest < ActiveRecord::TestCase
end
def test_relation_to_sql
- sql = Post.connection.unprepared_statement do
- Post.first.comments.to_sql
- end
+ sql = Post.first.comments.to_sql
assert_no_match(/\?/, sql)
end
@@ -81,31 +79,20 @@ class RelationMergingTest < ActiveRecord::TestCase
left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.where_values[1]] + right.where_values
+ expected = [left.bind_values[1]] + right.bind_values
merged = left.merge(right)
- assert_equal expected, merged.where_values
+ assert_equal expected, merged.bind_values
assert !merged.to_sql.include?("omg")
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
- def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: Arel::Nodes::BindParam.new('?'))
- column = Post.columns_hash['id']
- left.bind_values += [[column, 20]]
- right = Post.where(id: 10)
-
- merged = left.merge(right)
- assert_equal [], merged.bind_values
- end
-
def test_merging_keeps_lhs_bind_parameters
column = Post.columns_hash['id']
binds = [[column, 20]]
- right = Post.where(id: Arel::Nodes::BindParam.new('?'))
- right.bind_values += binds
+ right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
@@ -113,17 +100,9 @@ class RelationMergingTest < ActiveRecord::TestCase
end
def test_merging_reorders_bind_params
- post = Post.first
- id_column = Post.columns_hash['id']
- title_column = Post.columns_hash['title']
-
- bv = Post.connection.substitute_at id_column, 0
-
- right = Post.where(id: bv)
- right.bind_values += [[id_column, post.id]]
-
- left = Post.where(title: bv)
- left.bind_values += [[title_column, post.title]]
+ post = Post.first
+ right = Post.where(id: 1)
+ left = Post.where(title: post.title)
merged = left.merge(right)
assert_equal post, merged.first
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 7cb2a19bee..1da5c36e1c 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -14,19 +14,28 @@ module ActiveRecord
def relation_delegate_class(klass)
self.class.relation_delegate_class(klass)
end
+
+ def attribute_alias?(name)
+ false
+ end
end
def relation
@relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
end
end
+ test "#_select!" do
+ assert relation.public_send("_select!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("select_values")
+ end
+
test '#order!' do
assert relation.order!('name ASC').equal?(relation)
assert_equal ['name ASC'], relation.order_values
@@ -103,10 +112,18 @@ module ActiveRecord
end
test 'reverse_order!' do
- assert relation.reverse_order!.equal?(relation)
- assert relation.reverse_order_value
+ relation = Post.order('title ASC, comments_count DESC')
+
relation.reverse_order!
- assert !relation.reverse_order_value
+
+ assert_equal 'title DESC', relation.order_values.first
+ assert_equal 'comments_count ASC', relation.order_values.last
+
+
+ relation.reverse_order!
+
+ assert_equal 'title ASC', relation.order_values.first
+ assert_equal 'comments_count DESC', relation.order_values.last
end
test 'create_with!' do
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 14a8d97d36..4057835688 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -5,10 +5,10 @@ module ActiveRecord
class PredicateBuilderTest < ActiveRecord::TestCase
def test_registering_new_handlers
PredicateBuilder.register_handler(Regexp, proc do |column, value|
- Arel::Nodes::InfixOperation.new('~', column, value.source)
+ Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
end)
- assert_match %r{["`]topics["`].["`]title["`] ~ 'rails'}i, Topic.where(title: /rails/).to_sql
+ assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
end
end
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index fd2420cb88..c6decaad89 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -12,13 +12,19 @@ module ActiveRecord
end
def test_not_eq
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
relation = Post.where.not(title: 'hello')
- assert_equal([expected], relation.where_values)
+
+ assert_equal 1, relation.where_values.length
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'hello', bind.last
end
def test_not_null
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], nil)
+ expected = Post.arel_table[@name].not_eq(nil)
relation = Post.where.not(title: nil)
assert_equal([expected], relation.where_values)
end
@@ -30,13 +36,13 @@ module ActiveRecord
end
def test_not_in
- expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye])
+ expected = Post.arel_table[@name].not_in(%w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
assert_equal([expected], relation.where_values)
end
def test_association_not_eq
- expected = Arel::Nodes::NotEqual.new(Comment.arel_table[@name], 'hello')
+ expected = Comment.arel_table[@name].not_eq('hello')
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
@@ -44,21 +50,29 @@ module ActiveRecord
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'hello')
- assert_equal(expected, relation.where_values.first)
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'hello', bind.last
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'world')
- assert_equal(expected, relation.where_values.last)
+ value = relation.where_values.last
+ bind = relation.bind_values.last
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'world', bind.last
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
- assert_equal(expected, relation.where_values.first)
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'hello', bind.last
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'world')
- assert_equal(expected, relation.where_values.last)
+ value = relation.where_values.last
+ bind = relation.bind_values.last
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'world', bind.last
end
def test_not_eq_with_string_parameter
@@ -76,41 +90,64 @@ module ActiveRecord
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
- expected = Arel::Nodes::NotIn.new(Post.arel_table['author_id'], [1, 2])
+ expected = Post.arel_table['author_id'].not_in([1, 2])
assert_equal(expected, relation.where_values[0])
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'ruby on rails')
- assert_equal(expected, relation.where_values[1])
+ value = relation.where_values[1]
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'ruby on rails', bind.last
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'alone')
assert_equal 1, relation.where_values.size
- assert_equal expected, relation.where_values.first
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
end
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
- title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone')
- body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'again')
-
assert_equal 2, relation.where_values.size
- assert_equal title_expected, relation.where_values.first
- assert_equal body_expected, relation.where_values.second
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
+
+ value = relation.where_values[1]
+ bind = relation.bind_values[1]
+ assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
+ assert_equal 'again', bind.last
+ end
+
+ def assert_bound_ast value, table, type
+ assert_equal table, value.left
+ assert_kind_of type, value
+ assert_kind_of Arel::Nodes::BindParam, value.right
end
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
- title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone')
- body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'world')
-
assert_equal 2, relation.where_values.size
- assert_equal body_expected, relation.where_values.first
- assert_equal title_expected, relation.where_values.second
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
+ assert_equal 'world', bind.last
+
+ value = relation.where_values.second
+ bind = relation.bind_values.second
+
+ assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 15611656fd..fb0b906c07 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -16,6 +16,10 @@ module ActiveRecord
def self.connection
Post.connection
end
+
+ def self.table_name
+ 'fake_table'
+ end
end
def test_construction
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index afd5a69cef..68e62934c1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -14,6 +14,7 @@ require 'models/car'
require 'models/engine'
require 'models/tyre'
require 'models/minivan'
+require 'models/aircraft'
class RelationTest < ActiveRecord::TestCase
@@ -65,7 +66,7 @@ class RelationTest < ActiveRecord::TestCase
def test_scoped
topics = Topic.all
assert_kind_of ActiveRecord::Relation, topics
- assert_equal 4, topics.size
+ assert_equal 5, topics.size
end
def test_to_json
@@ -86,14 +87,14 @@ class RelationTest < ActiveRecord::TestCase
def test_scoped_all
topics = Topic.all.to_a
assert_kind_of Array, topics
- assert_no_queries { assert_equal 4, topics.size }
+ assert_no_queries { assert_equal 5, topics.size }
end
def test_loaded_all
topics = Topic.all
assert_queries(1) do
- 2.times { assert_equal 4, topics.to_a.size }
+ 2.times { assert_equal 5, topics.to_a.size }
end
assert topics.loaded?
@@ -151,11 +152,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a
end
- def test_finding_with_subquery_without_select
- relation = Topic.where(:approved => true)
- assert_equal relation.to_a, Topic.from(relation).to_a
+ def test_finding_with_subquery_without_select_does_not_change_the_select
+ relation = Topic.where(approved: true)
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Topic.from(relation).to_a
+ end
end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -164,52 +168,100 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_order
topics = Topic.order('id')
- assert_equal 4, topics.to_a.size
+ assert_equal 5, topics.to_a.size
assert_equal topics(:first).title, topics.first.title
end
-
def test_finding_with_arel_order
topics = Topic.order(Topic.arel_table[:id].asc)
- assert_equal 4, topics.to_a.size
+ assert_equal 5, topics.to_a.size
assert_equal topics(:first).title, topics.first.title
end
def test_finding_with_assoc_order
topics = Topic.order(:id => :desc)
- assert_equal 4, topics.to_a.size
- assert_equal topics(:fourth).title, topics.first.title
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
end
def test_finding_with_reverted_assoc_order
topics = Topic.order(:id => :asc).reverse_order
- assert_equal 4, topics.to_a.size
- assert_equal topics(:fourth).title, topics.first.title
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
end
def test_order_with_hash_and_symbol_generates_the_same_sql
assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
end
+ def test_finding_with_desc_order_with_string
+ topics = Topic.order(id: "desc")
+ assert_equal 5, topics.to_a.size
+ assert_equal [topics(:fifth), topics(:fourth), topics(:third), topics(:second), topics(:first)], topics.to_a
+ end
+
+ def test_finding_with_asc_order_with_string
+ topics = Topic.order(id: 'asc')
+ assert_equal 5, topics.to_a.size
+ assert_equal [topics(:first), topics(:second), topics(:third), topics(:fourth), topics(:fifth)], topics.to_a
+ end
+
+ def test_support_upper_and_lower_case_directions
+ assert_includes Topic.order(id: "ASC").to_sql, "ASC"
+ assert_includes Topic.order(id: "asc").to_sql, "ASC"
+ assert_includes Topic.order(id: :ASC).to_sql, "ASC"
+ assert_includes Topic.order(id: :asc).to_sql, "ASC"
+
+ assert_includes Topic.order(id: "DESC").to_sql, "DESC"
+ assert_includes Topic.order(id: "desc").to_sql, "DESC"
+ assert_includes Topic.order(id: :DESC).to_sql, "DESC"
+ assert_includes Topic.order(id: :desc).to_sql,"DESC"
+ end
+
def test_raising_exception_on_invalid_hash_params
- assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) }
+ e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) }
+ assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message
end
def test_finding_last_with_arel_order
topics = Topic.order(Topic.arel_table[:id].asc)
- assert_equal topics(:fourth).title, topics.last.title
+ assert_equal topics(:fifth).title, topics.last.title
end
def test_finding_with_order_concatenated
topics = Topic.order('author_name').order('title')
- assert_equal 4, topics.to_a.size
+ assert_equal 5, topics.to_a.size
assert_equal topics(:fourth).title, topics.first.title
end
+ def test_finding_with_order_by_aliased_attributes
+ topics = Topic.order(:heading)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_finding_with_assoc_order_by_aliased_attributes
+ topics = Topic.order(heading: :desc)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:third).title, topics.first.title
+ end
+
def test_finding_with_reorder
topics = Topic.order('author_name').order('title').reorder('id').to_a
topics_titles = topics.map{ |t| t.title }
- assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles
+ 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
+
+ def test_finding_with_reorder_by_aliased_attributes
+ topics = Topic.order('author_name').reorder(:heading)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_finding_with_assoc_reorder_by_aliased_attributes
+ topics = Topic.order('author_name').reorder(heading: :desc)
+ assert_equal 5, topics.to_a.size
+ assert_equal topics(:third).title, topics.first.title
end
def test_finding_with_order_and_take
@@ -314,6 +366,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
end
+
+ def test_null_relation_count
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -522,6 +584,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal expected, actual
end
+ def test_to_sql_on_scoped_proxy
+ auth = Author.first
+ Post.where("1=1").written_by(auth)
+ assert_not auth.posts.to_sql.include?("1=1")
+ end
+
def test_loading_with_one_association_with_non_preload
posts = Post.eager_load(:last_comment).order('comments.id DESC')
post = posts.find { |p| p.id == 1 }
@@ -586,7 +654,7 @@ class RelationTest < ActiveRecord::TestCase
def test_find_with_list_of_ar
author = Author.first
- authors = Author.find([author])
+ authors = Author.find([author.id])
assert_equal author, authors.first
end
@@ -718,7 +786,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.exists?(authors(:mary).id)
assert ! davids.exists?("42")
assert ! davids.exists?(42)
- assert ! davids.exists?(davids.new)
+ assert ! davids.exists?(davids.new.id)
fake = Author.where(:name => 'fake author')
assert ! fake.exists?
@@ -767,6 +835,16 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
end
+ def test_select_with_aggregates
+ posts = Post.select(:title, :body)
+
+ assert_equal 11, posts.count(:all)
+ assert_equal 11, posts.size
+ assert posts.any?
+ assert posts.many?
+ assert_not posts.empty?
+ end
+
def test_select_takes_a_variable_list_of_args
david = developers(:david)
@@ -775,6 +853,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal david.salary, developer.salary
end
+ def test_select_takes_an_aliased_attribute
+ first = topics(:first)
+
+ topic = Topic.where(id: first.id).select(:heading).first
+ assert_equal first.heading, topic.heading
+ end
+
def test_select_argument_error
assert_raises(ArgumentError) { Developer.select }
end
@@ -790,6 +875,17 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_on_association_relation
+ author = Author.last
+ another_author = Author.first
+ posts = Post.where(author_id: author.id)
+
+ assert_equal author.posts.where(author_id: author.id).size, posts.count
+
+ assert_equal 0, author.posts.where(author_id: another_author.id).size
+ assert author.posts.where(author_id: another_author.id).empty?
+ end
+
def test_count_with_distinct
posts = Post.all
@@ -800,6 +896,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.distinct(false).select(:comments_count).count
end
+ def test_update_all_with_scope
+ tag = Tag.first
+ Post.tagged_with(tag.id).update_all title: "rofl"
+ list = Post.tagged_with(tag.id).all.to_a
+ assert_operator list.length, :>, 0
+ list.each { |post| assert_equal 'rofl', post.title }
+ end
+
def test_count_explicit_columns
Post.update_all(:comments_count => nil)
posts = Post.all
@@ -1308,6 +1412,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['comments'], scope.references_values
end
+ def test_automatically_added_where_not_references
+ scope = Post.where.not(comments: { body: "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.where.not('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
def test_automatically_added_having_references
scope = Post.having(:comments => { :body => "Bla" })
assert_equal ['comments'], scope.references_values
@@ -1352,6 +1464,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], scope.references_values
end
+ def test_order_with_reorder_nil_removes_the_order
+ relation = Post.order(:title).reorder(nil)
+
+ assert_nil relation.order_values.first
+ end
+
+ def test_reverse_order_with_reorder_nil_removes_the_order
+ relation = Post.order(:title).reverse_order.reorder(nil)
+
+ assert_nil relation.order_values.first
+ end
+
def test_presence
topics = Topic.all
@@ -1505,6 +1629,12 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "joins with select" do
+ posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3)
+ assert_equal [1, 2, 4], posts.map(&:id)
+ assert_equal [1, 1, 1], posts.map(&:author_address_id)
+ end
+
test "delegations do not leak to other classes" do
Topic.all.by_lifo
assert Topic.all.class.method_defined?(:by_lifo)
@@ -1521,10 +1651,8 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: Arel::Nodes::BindParam.new('?'))
- column = Post.columns_hash['id']
- left.bind_values += [[column, 20]]
- right = Post.where(id: 10)
+ left = Post.where(id: 20)
+ right = Post.where(id: [1,2,3,4])
merged = left.merge(right)
assert_equal [], merged.bind_values
@@ -1534,8 +1662,7 @@ class RelationTest < ActiveRecord::TestCase
column = Post.columns_hash['id']
binds = [[column, 20]]
- right = Post.where(id: Arel::Nodes::BindParam.new('?'))
- right.bind_values += binds
+ right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
@@ -1543,17 +1670,9 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_reorders_bind_params
- post = Post.first
- id_column = Post.columns_hash['id']
- title_column = Post.columns_hash['title']
-
- bv = Post.connection.substitute_at id_column, 0
-
- right = Post.where(id: bv)
- right.bind_values += [[id_column, post.id]]
-
- left = Post.where(title: bv)
- left.bind_values += [[title_column, post.title]]
+ post = Post.first
+ right = Post.where(id: post.id)
+ left = Post.where(title: post.title)
merged = left.merge(right)
assert_equal post, merged.first
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index b6c583dbf5..2131b32a0c 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -5,14 +5,16 @@ module ActiveRecord
def result
Result.new(['col_1', 'col_2'], [
['row 1 col 1', 'row 1 col 2'],
- ['row 2 col 1', 'row 2 col 2']
+ ['row 2 col 1', 'row 2 col 2'],
+ ['row 3 col 1', 'row 3 col 2'],
])
end
def test_to_hash_returns_row_hashes
assert_equal [
{'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'},
- {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'}
+ {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'},
+ {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'},
], result.to_hash
end
@@ -28,5 +30,11 @@ module ActiveRecord
assert_kind_of Integer, index
end
end
+
+ if Enumerator.method_defined? :size
+ def test_each_without_block_returns_a_sized_enumerator
+ assert_equal 3, result.each.size
+ end
+ end
end
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 766b2ff2ef..dca85fb5eb 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -46,4 +46,36 @@ class SanitizeTest < ActiveRecord::TestCase
select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts])
assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables')
end
+
+ def test_sanitize_sql_array_handles_empty_statement
+ select_author_sql = Post.send(:sanitize_sql_array, [''])
+ assert_equal('', select_author_sql)
+ end
+
+ def test_sanitize_sql_like
+ assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%')
+ assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string')
+ assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42')
+ end
+
+ def test_sanitize_sql_like_with_custom_escape_character
+ assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!')
+ assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!')
+ assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!')
+ assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!')
+ end
+
+ def test_sanitize_sql_like_example_use_case
+ searchable_post = Class.new(Post) do
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term, '!'))
+ end
+ end
+
+ assert_sql(/LIKE '20!% !_reduction!_!!'/) do
+ searchable_post.search("20% _reduction_!").to_a
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index c085663efb..fd0ef2f89f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -63,7 +63,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/)
match[0].length
end
end
@@ -177,7 +177,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
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) || current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
@@ -188,7 +188,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
@@ -319,6 +319,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
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
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 71754cf0a2..9a4d8c6740 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -149,6 +149,16 @@ class DefaultScopingTest < ActiveRecord::TestCase
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 }
+
+ dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago)
+ received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.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 }
@@ -385,4 +395,22 @@ class DefaultScopingTest < ActiveRecord::TestCase
threads.each(&:join)
end
end
+
+ test "additional conditions are ANDed with the default scope" do
+ scope = DeveloperCalledJamis.where(name: "David")
+ assert_equal 2, scope.where_values.length
+ assert_equal [], scope.to_a
+ end
+
+ test "additional conditions in a scope are ANDed with the default scope" do
+ scope = DeveloperCalledJamis.david
+ assert_equal 2, scope.where_values.length
+ assert_equal [], scope.to_a
+ end
+
+ test "a scope can remove the condition from the default scope" do
+ scope = DeveloperCalledJamis.david2
+ assert_equal 1, scope.where_values.length
+ assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
+ end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 72c9787b84..59ec2dd6a4 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -266,6 +266,68 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 'lifo', topic.author_name
end
+ def test_reserved_scope_names
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+
+ scope :approved, -> { where(approved: true) }
+
+ class << self
+ public
+ def pub; end
+
+ private
+ def pri; end
+
+ protected
+ def pro; end
+ end
+ end
+
+ subklass = Class.new(klass)
+
+ conflicts = [
+ :create, # public class method on AR::Base
+ :relation, # private class method on AR::Base
+ :new, # redefined class method on AR::Base
+ :all, # a default scope
+ :public,
+ :protected,
+ :private
+ ]
+
+ non_conflicts = [
+ :find_by_title, # dynamic finder method
+ :approved, # existing scope
+ :pub, # existing public class method
+ :pri, # existing private class method
+ :pro, # existing protected class method
+ :open, # a ::Kernel method
+ ]
+
+ conflicts.each do |name|
+ assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
+ klass.class_eval { scope name, ->{ where(approved: true) } }
+ end
+
+ assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
+ subklass.class_eval { scope name, ->{ where(approved: true) } }
+ end
+ end
+
+ non_conflicts.each do |name|
+ assert_nothing_raised do
+ silence_warnings do
+ klass.class_eval { scope name, ->{ where(approved: true) } }
+ end
+ end
+
+ assert_nothing_raised do
+ subklass.class_eval { scope name, ->{ where(approved: true) } }
+ end
+ end
+ end
+
# Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/
# has been done by evaluating a string with a plain def statement. For scope
# names which contain spaces this approach doesn't work.
@@ -344,13 +406,13 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scopes_batch_finders
- assert_equal 3, Topic.approved.count
+ assert_equal 4, Topic.approved.count
- assert_queries(4) do
+ assert_queries(5) do
Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? }
end
- assert_queries(2) do
+ assert_queries(3) do
Topic.approved.find_in_batches(:batch_size => 2) do |group|
group.each {|t| assert t.approved? }
end
@@ -366,7 +428,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_scopes_on_relations
# Topic.replied
approved_topics = Topic.all.approved.order('id DESC')
- assert_equal topics(:fourth), approved_topics.first
+ assert_equal topics(:fifth), approved_topics.first
replied_approved_topics = approved_topics.replied
assert_equal topics(:third), replied_approved_topics.first
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 0018fc06f2..d8a467ec4d 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -192,8 +192,9 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
Developer.where('salary = 80000').scoping do
Developer.limit(10).scoping do
devs = Developer.all
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
+ sql = devs.to_sql
+ assert_match '(salary = 80000)', sql
+ assert_match 'LIMIT 10', sql
end
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index bc67da8d27..5609cf310c 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -10,8 +10,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
MyObject = Struct.new :attribute1, :attribute2
- def teardown
- super
+ teardown do
Topic.serialize("content")
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index 76da49707f..a704b861cb 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -10,27 +10,61 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
+ #Cache v 1.1 tests
+ def test_statement_cache
+ Book.create(name: "my book")
+ Book.create(name: "my other book")
+
+ cache = StatementCache.create(Book.connection) do |params|
+ Book.where(:name => params.bind)
+ end
+
+ b = cache.execute([ "my book" ], Book, Book.connection)
+ assert_equal "my book", b[0].name
+ b = cache.execute([ "my other book" ], Book, Book.connection)
+ assert_equal "my other book", b[0].name
+ end
+
+
+ def test_statement_cache_id
+ b1 = Book.create(name: "my book")
+ b2 = Book.create(name: "my other book")
+
+ cache = StatementCache.create(Book.connection) do |params|
+ Book.where(id: params.bind)
+ end
+
+ b = cache.execute([ b1.id ], Book, Book.connection)
+ assert_equal b1.name, b[0].name
+ b = cache.execute([ b2.id ], Book, Book.connection)
+ assert_equal b2.name, b[0].name
+ end
+
+ def test_find_or_create_by
+ Book.create(name: "my book")
+
+ a = Book.find_or_create_by(name: "my book")
+ b = Book.find_or_create_by(name: "my other book")
+
+ assert_equal("my book", a.name)
+ assert_equal("my other book", b.name)
+ end
+
+ #End
+
def test_statement_cache_with_simple_statement
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book").where("author_id > 3")
end
Book.create(name: "my book", author_id: 4)
- books = cache.execute
+ books = cache.execute([], Book, Book.connection)
assert_equal "my book", books[0].name
end
- def test_statement_cache_with_nil_statement_raises_error
- assert_raise(ArgumentError) do
- ActiveRecord::StatementCache.new do
- nil
- end
- end
- end
-
def test_statement_cache_with_complex_statement
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton')
end
@@ -38,12 +72,12 @@ module ActiveRecord
molecule = salty.molecules.create(name: 'dioxane')
molecule.electrons.create(name: 'lepton')
- liquids = cache.execute
+ liquids = cache.execute([], Book, Book.connection)
assert_equal "salty", liquids[0].name
end
def test_statement_cache_values_differ
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book")
end
@@ -51,13 +85,13 @@ module ActiveRecord
Book.create(name: "my book")
end
- first_books = cache.execute
+ first_books = cache.execute([], Book, Book.connection)
3.times do
Book.create(name: "my book")
end
- additional_books = cache.execute
+ additional_books = cache.execute([], Book, Book.connection)
assert first_books != additional_books
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 6f632b4d8d..978cee9cfb 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -166,4 +166,28 @@ class StoreTest < ActiveRecord::TestCase
test "YAML coder initializes the store when a Nil value is given" do
assert_equal({}, @john.params)
end
+
+ test "attributes_for_coder should return stored fields already serialized" do
+ attributes = {
+ "id" => @john.id,
+ "name"=> @john.name,
+ "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n",
+ "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n",
+ "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}",
+ "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n",
+ "account_id"=> @john.account_id
+ }
+
+ assert_equal attributes, @john.attributes_for_coder
+ end
+
+ test "dump, load and dump again a model" do
+ dumped = YAML.dump(@john)
+ loaded = YAML.load(dumped)
+ assert_equal @john, loaded
+
+ second_dump = YAML.dump(loaded)
+ assert_equal dumped, second_dump
+ assert_equal @john, YAML.load(second_dump)
+ end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 4476ce3410..803a054d7e 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -19,10 +19,14 @@ module ActiveRecord
end
end
- def assert_sql(*patterns_to_match)
+ def capture_sql
SQLCounter.clear_log
yield
- SQLCounter.log_all
+ SQLCounter.log_all.dup
+ end
+
+ def assert_sql(*patterns_to_match)
+ capture_sql { yield }
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 717e0e1866..77ab427be0 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -71,6 +71,24 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal @previously_updated_at, @developer.updated_at
end
+ def test_saving_when_callback_sets_record_timestamps_to_false_doesnt_update_its_timestamp
+ klass = Class.new(Developer) do
+ before_update :cancel_record_timestamps
+ def cancel_record_timestamps
+ self.record_timestamps = false
+ return true
+ end
+ end
+
+ developer = klass.first
+ previously_updated_at = developer.updated_at
+
+ developer.name = "New Name"
+ developer.save!
+
+ assert_equal previously_updated_at, developer.updated_at
+ end
+
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
@@ -89,6 +107,18 @@ class TimestampTest < ActiveRecord::TestCase
assert_in_delta Time.now, task.ending, 1
end
+ def test_touching_many_attributes_updates_them
+ task = Task.first
+ previous_starting = task.starting
+ previous_ending = task.ending
+ task.touch(:starting, :ending)
+
+ assert_not_equal previous_starting, task.starting
+ assert_not_equal previous_ending, task.ending
+ assert_in_delta Time.now, task.starting, 1
+ assert_in_delta Time.now, task.ending, 1
+ end
+
def test_touching_a_record_without_timestamps_is_unexceptional
assert_nothing_raised { Car.first.touch }
end
@@ -140,6 +170,25 @@ class TimestampTest < ActiveRecord::TestCase
assert !@developer.no_touching?
end
+ def test_no_touching_with_callbacks
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers"
+
+ attr_accessor :after_touch_called
+
+ after_touch do |user|
+ user.after_touch_called = true
+ end
+ end
+
+ developer = klass.first
+
+ klass.no_touching do
+ developer.touch
+ assert_not developer.after_touch_called
+ end
+ end
+
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
pet = Pet.first
owner = pet.owner
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 5644a35385..3d64ecb464 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -1,9 +1,11 @@
require "cases/helper"
+require 'models/owner'
+require 'models/pet'
require 'models/topic'
class TransactionCallbacksTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- fixtures :topics
+ fixtures :topics, :owners, :pets
class ReplyWithCallbacks < ActiveRecord::Base
self.table_name = :topics
@@ -14,6 +16,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
after_commit :do_after_commit, on: :create
+ attr_accessor :save_on_after_create
+ after_create do
+ self.save! if save_on_after_create
+ end
+
def history
@history ||= []
end
@@ -28,14 +35,14 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
- after_commit{|record| record.send(:do_after_commit, nil)}
- after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
- after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
- after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)}
- after_rollback{|record| record.send(:do_after_rollback, nil)}
- after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)}
- after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)}
- after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)}
+ after_commit { |record| record.do_after_commit(nil) }
+ after_commit(on: :create) { |record| record.do_after_commit(:create) }
+ after_commit(on: :update) { |record| record.do_after_commit(:update) }
+ after_commit(on: :destroy) { |record| record.do_after_commit(:destroy) }
+ after_rollback { |record| record.do_after_rollback(nil) }
+ after_rollback(on: :create) { |record| record.do_after_rollback(:create) }
+ after_rollback(on: :update) { |record| record.do_after_rollback(:update) }
+ after_rollback(on: :destroy) { |record| record.do_after_rollback(:destroy) }
def history
@history ||= []
@@ -65,7 +72,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def setup
- @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id }
+ @first = TopicWithCallbacks.find(1)
end
def test_call_after_commit_after_transaction_commits
@@ -77,40 +84,25 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
@first.save!
assert_equal [:commit_on_update], @first.history
end
def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
@first.destroy
assert_equal [:commit_on_destroy], @first.history
end
def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @new_record.save!
- assert_equal [:commit_on_create], @new_record.history
+ new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ add_transaction_execution_blocks new_record
+
+ new_record.save!
+ assert_equal [:commit_on_create], new_record.history
end
def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association
@@ -120,6 +112,23 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [], reply.history
end
+ def test_only_call_after_commit_on_create_and_doesnt_leaky
+ r = ReplyWithCallbacks.new(content: 'foo')
+ r.save_on_after_create = true
+ r.save!
+ r.content = 'bar'
+ r.save!
+ r.save!
+ assert_equal [:commit_on_create], r.history
+ end
+
+ def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record_on_touch
+ add_transaction_execution_blocks @first
+
+ @first.touch
+ assert_equal [:commit_on_update], @first.history
+ end
+
def test_call_after_rollback_after_transaction_rollsback
@first.after_commit_block{|r| r.history << :after_commit}
@first.after_rollback_block{|r| r.history << :after_rollback}
@@ -133,12 +142,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
Topic.transaction do
@first.save!
@@ -148,13 +152,19 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:rollback_on_update], @first.history
end
+ def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record_on_touch
+ add_transaction_execution_blocks @first
+
+ Topic.transaction do
+ @first.touch
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal [:rollback_on_update], @first.history
+ end
+
def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_update}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ add_transaction_execution_blocks @first
Topic.transaction do
@first.destroy
@@ -165,20 +175,15 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
+ new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
+ add_transaction_execution_blocks new_record
Topic.transaction do
- @new_record.save!
+ new_record.save!
raise ActiveRecord::Rollback
end
- assert_equal [:rollback_on_create], @new_record.history
+ assert_equal [:rollback_on_create], new_record.history
end
def test_call_after_rollback_when_commit_fails
@@ -205,23 +210,24 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@first.after_rollback_block{|r| r.rollbacks(1)}
@first.after_commit_block{|r| r.commits(1)}
- def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @second.commits(i=0); @commits ||= 0; @commits += i if i; end
- @second.after_rollback_block{|r| r.rollbacks(1)}
- @second.after_commit_block{|r| r.commits(1)}
+ second = TopicWithCallbacks.find(3)
+ def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def second.commits(i=0); @commits ||= 0; @commits += i if i; end
+ second.after_rollback_block{|r| r.rollbacks(1)}
+ second.after_commit_block{|r| r.commits(1)}
Topic.transaction do
@first.save!
Topic.transaction(:requires_new => true) do
- @second.save!
+ second.save!
raise ActiveRecord::Rollback
end
end
assert_equal 1, @first.commits
assert_equal 0, @first.rollbacks
- assert_equal 0, @second.commits
- assert_equal 1, @second.rollbacks
+ assert_equal 0, second.commits
+ assert_equal 1, second.rollbacks
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
@@ -252,33 +258,61 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
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.after_commit_block{|r| r.history << :after_commit}
- @second.after_rollback_block{|r| r.history << :after_rollback}
+
+ 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!
+ second.save!
end
assert_equal :commit, @first.last_after_transaction_error
- assert_equal [:after_commit], @second.history
+ assert_equal [:after_commit], second.history
- @second.history.clear
+ second.history.clear
Topic.transaction do
@first.save!
- @second.save!
+ second.save!
raise ActiveRecord::Rollback
end
assert_equal :rollback, @first.last_after_transaction_error
- assert_equal [:after_rollback], @second.history
+ assert_equal [:after_rollback], second.history
end
def test_after_rollback_callbacks_should_validate_on_condition
- assert_raise(ArgumentError) { Topic.send(:after_rollback, :on => :save) }
+ assert_raise(ArgumentError) { Topic.after_rollback(on: :save) }
end
def test_after_commit_callbacks_should_validate_on_condition
- assert_raise(ArgumentError) { Topic.send(:after_commit, :on => :save) }
+ assert_raise(ArgumentError) { Topic.after_commit(on: :save) }
end
+
+ def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
+ pet = Pet.first
+ owner = pet.owner
+ flag = false
+
+ owner.on_after_commit do
+ flag = true
+ end
+
+ pet.name = "Fluffy the Third"
+ pet.save
+
+ assert flag
+ end
+
+ private
+
+ def add_transaction_execution_blocks(record)
+ record.after_commit_block(:create) { |r| r.history << :commit_on_create }
+ record.after_commit_block(:update) { |r| r.history << :commit_on_update }
+ record.after_commit_block(:destroy) { |r| r.history << :commit_on_destroy }
+ record.after_rollback_block(:create) { |r| r.history << :rollback_on_create }
+ record.after_rollback_block(:update) { |r| r.history << :rollback_on_update }
+ record.after_rollback_block(:destroy) { |r| r.history << :rollback_on_destroy }
+ end
end
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 89dab16975..e6ed85394b 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -5,6 +5,7 @@ require 'models/developer'
require 'models/book'
require 'models/author'
require 'models/post'
+require 'models/movie'
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -14,6 +15,11 @@ class TransactionTest < ActiveRecord::TestCase
@first, @second = Topic.find(1, 2).sort_by { |t| t.id }
end
+ def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
+ movie = Movie.create
+ assert !movie.persisted?
+ end
+
def test_raise_after_destroy
assert_not @first.frozen?
@@ -430,17 +436,26 @@ class TransactionTest < ActiveRecord::TestCase
end
def test_restore_active_record_state_for_all_records_in_a_transaction
+ topic_without_callbacks = Class.new(ActiveRecord::Base) do
+ self.table_name = 'topics'
+ end
+
topic_1 = Topic.new(:title => 'test_1')
topic_2 = Topic.new(:title => 'test_2')
+ topic_3 = topic_without_callbacks.new(:title => 'test_3')
+
Topic.transaction do
assert topic_1.save
assert topic_2.save
+ assert topic_3.save
@first.save
@second.destroy
assert topic_1.persisted?, 'persisted'
assert_not_nil topic_1.id
assert topic_2.persisted?, 'persisted'
assert_not_nil topic_2.id
+ assert topic_3.persisted?, 'persisted'
+ assert_not_nil topic_3.id
assert @first.persisted?, 'persisted'
assert_not_nil @first.id
assert @second.destroyed?, 'destroyed'
@@ -451,6 +466,8 @@ class TransactionTest < ActiveRecord::TestCase
assert_nil topic_1.id
assert !topic_2.persisted?, 'not persisted'
assert_nil topic_2.id
+ assert !topic_3.persisted?, 'not persisted'
+ assert_nil topic_3.id
assert @first.persisted?, 'persisted'
assert_not_nil @first.id
assert !@second.destroyed?, 'not destroyed'
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index e82ca3f93d..afb893a52c 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -11,7 +11,7 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
@specification = ActiveRecord::Base.remove_connection
end
- def teardown
+ teardown do
@underlying = nil
ActiveRecord::Base.establish_connection(@specification)
load_schema if in_memory_db?
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 602f633c45..e4edc437e6 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -1,44 +1,14 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
-require 'models/owner'
-require 'models/pet'
require 'models/man'
require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
- fixtures :topics, :owners
+ fixtures :topics
repair_validations(Topic, Reply)
- def test_validates_size_of_association
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'apet')
- assert o.valid?
- end
- end
-
- def test_validates_size_of_association_using_within
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
-
- o.pets.build('name' => 'apet')
- assert o.valid?
-
- 2.times { o.pets.build('name' => 'apet') }
- assert !o.save
- assert o.errors[:pets].any?
- end
- end
-
def test_validates_associated_many
Topic.validates_associated(:replies)
Reply.validates_presence_of(:content)
@@ -94,17 +64,6 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert r.valid?
end
- def test_validates_size_of_association_utf8
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'あいうえおかきくけこ')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'あいうえおかきくけこ')
- assert o.valid?
- end
- end
-
def test_validates_presence_of_belongs_to_association__parent_is_new_record
repair_validations(Interest) do
# Note that Interest and Man have the :inverse_of option set
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index a73c3bf1af..13d4d85afa 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -3,7 +3,7 @@ require 'models/topic'
class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
def setup
- Topic.reset_callbacks(:validate)
+ Topic.clear_validators!
@topic = Topic.new
I18n.backend = I18n::Backend::Simple.new
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index efa0c9b934..3db742c15b 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -14,7 +14,7 @@ class I18nValidationTest < ActiveRecord::TestCase
I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}})
end
- def teardown
+ teardown do
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
end
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
new file mode 100644
index 0000000000..4a92da38ce
--- /dev/null
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'models/owner'
+require 'models/pet'
+
+class LengthValidationTest < ActiveRecord::TestCase
+ fixtures :owners
+ repair_validations(Owner)
+
+ def test_validates_size_of_association
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+ end
+ end
+
+ def test_validates_size_of_association_using_within
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors[:pets].any?
+ end
+ end
+
+ def test_validates_size_of_association_utf8
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 74c696c858..18221cc73d 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -223,7 +223,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t_utf8.save, "Should save t_utf8 as unique"
# If database hasn't UTF-8 character set, this test fails
- if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!"
+ if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8.id).title == "я тоже уникальный!"
t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
assert !t2_utf8.valid?, "Shouldn't be valid"
assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index de618902aa..d80da06e27 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -52,6 +52,21 @@ class ValidationsTest < ActiveRecord::TestCase
assert r.save(:context => :special_case)
end
+ def test_validate
+ r = WrongReply.new
+
+ r.validate
+ assert_empty r.errors[:author_name]
+
+ r.validate(:special_case)
+ assert_not_empty r.errors[:author_name]
+
+ r.author_name = "secret"
+
+ r.validate(:special_case)
+ assert_empty r.errors[:author_name]
+ end
+
def test_invalid_record_exception
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 78fa2f935a..3cb617497d 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "rexml/document"
require 'models/contact'
require 'models/post'
require 'models/author'
diff --git a/activerecord/test/fixtures/companies.yml b/activerecord/test/fixtures/companies.yml
index 0766e92027..ab9d5378ad 100644
--- a/activerecord/test/fixtures/companies.yml
+++ b/activerecord/test/fixtures/companies.yml
@@ -57,3 +57,11 @@ odegy:
id: 9
name: Odegy
type: ExclusivelyDependentFirm
+
+another_first_firm_client:
+ id: 11
+ type: Client
+ firm_id: 1
+ client_of: 1
+ name: Apex
+ firm_name: 37signals
diff --git a/activerecord/test/fixtures/computers.yml b/activerecord/test/fixtures/computers.yml
index daf969d7da..7281a4d768 100644
--- a/activerecord/test/fixtures/computers.yml
+++ b/activerecord/test/fixtures/computers.yml
@@ -1,4 +1,5 @@
workstation:
id: 1
+ system: 'Linux'
developer: 1
extendedWarranty: 1
diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml
index 6004f390a4..1bb3bf0051 100644
--- a/activerecord/test/fixtures/pirates.yml
+++ b/activerecord/test/fixtures/pirates.yml
@@ -7,3 +7,6 @@ redbeard:
parrot: louis
created_on: "<%= 2.weeks.ago.to_s(:db) %>"
updated_on: "<%= 2.weeks.ago.to_s(:db) %>"
+
+mark:
+ catchphrase: "X $LABELs the spot!"
diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml
index 2b042bd135..bf049abbf1 100644
--- a/activerecord/test/fixtures/topics.yml
+++ b/activerecord/test/fixtures/topics.yml
@@ -40,3 +40,10 @@ fourth:
type: Reply
parent_id: 3
+fifth:
+ id: 5
+ title: The Fifth Topic of the day
+ author_name: Jason
+ written_on: 2013-07-13t12:11:00.0099+01:00
+ content: Omakase
+ approved: true
diff --git a/activerecord/test/fixtures/uuid_children.yml b/activerecord/test/fixtures/uuid_children.yml
new file mode 100644
index 0000000000..a7b15016e2
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_children.yml
@@ -0,0 +1,3 @@
+sonny:
+ uuid_parent: daddy
+ name: Sonny
diff --git a/activerecord/test/fixtures/uuid_parents.yml b/activerecord/test/fixtures/uuid_parents.yml
new file mode 100644
index 0000000000..0b40225c5c
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_parents.yml
@@ -0,0 +1,2 @@
+daddy:
+ name: Daddy
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 4cb2c7606b..2170018068 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -9,6 +9,7 @@ class Book < ActiveRecord::Base
enum status: [:proposed, :written, :published]
enum read_status: {unread: 0, reading: 2, read: 3}
+ enum nullable_status: [:single, :married]
def published!
super
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index c4a15a79e2..db0f93f63b 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -15,7 +15,6 @@ class Car < ActiveRecord::Base
scope :incl_engines, -> { includes(:engines) }
scope :order_using_new_style, -> { order('name asc') }
-
end
class CoolCar < Car
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 7da39a8e33..272223e1d8 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -22,6 +22,7 @@ class Category < ActiveRecord::Base
end
has_many :categorizations
+ has_many :special_categorizations
has_many :post_comments, :through => :posts, :source => :comments
has_many :authors, :through => :categorizations
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 566e0873f1..a762ad4bb5 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -6,6 +6,8 @@ class Club < ActiveRecord::Base
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
belongs_to :category
+ has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member
+
private
def private_method
diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb
index c7495d7deb..501af4a8dd 100644
--- a/activerecord/test/models/college.rb
+++ b/activerecord/test/models/college.rb
@@ -1,5 +1,10 @@
require_dependency 'models/arunit2_model'
+require 'active_support/core_ext/object/with_options'
class College < ARUnit2Model
has_many :courses
+
+ with_options dependent: :destroy do |assoc|
+ assoc.has_many :students, -> { where(active: true) }
+ end
end
diff --git a/activerecord/test/models/column.rb b/activerecord/test/models/column.rb
new file mode 100644
index 0000000000..499358b4cf
--- /dev/null
+++ b/activerecord/test/models/column.rb
@@ -0,0 +1,3 @@
+class Column < ActiveRecord::Base
+ belongs_to :record
+end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 2e2d8a0d37..762259ffa3 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -165,6 +165,8 @@ class DeveloperCalledJamis < ActiveRecord::Base
default_scope { where(:name => 'Jamis') }
scope :poor, -> { where('salary < 150000') }
+ scope :david, -> { where name: "David" }
+ scope :david2, -> { unscoped.where name: "David" }
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb
index 35af9f679b..6fc270673f 100644
--- a/activerecord/test/models/electron.rb
+++ b/activerecord/test/models/electron.rb
@@ -1,3 +1,5 @@
class Electron < ActiveRecord::Base
belongs_to :molecule
+
+ validates_presence_of :name
end
diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb
index 4d37371777..1c35006665 100644
--- a/activerecord/test/models/mixed_case_monkey.rb
+++ b/activerecord/test/models/mixed_case_monkey.rb
@@ -1,5 +1,3 @@
class MixedCaseMonkey < ActiveRecord::Base
- self.primary_key = 'monkeyID'
-
belongs_to :man
end
diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb
index 69325b8d29..26870c8f88 100644
--- a/activerecord/test/models/molecule.rb
+++ b/activerecord/test/models/molecule.rb
@@ -1,4 +1,6 @@
class Molecule < ActiveRecord::Base
belongs_to :liquid
has_many :electrons
+
+ accepts_nested_attributes_for :electrons
end
diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb
index c441be2bef..0302abad1e 100644
--- a/activerecord/test/models/movie.rb
+++ b/activerecord/test/models/movie.rb
@@ -1,3 +1,5 @@
class Movie < ActiveRecord::Base
self.primary_key = "movieid"
+
+ validates_presence_of :name
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 1c7ed4aa3e..cf24502d3a 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -2,4 +2,21 @@ class Owner < ActiveRecord::Base
self.primary_key = :owner_id
has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
+
+ after_commit :execute_blocks
+
+ def blocks
+ @blocks ||= []
+ end
+
+ def on_after_commit(&block)
+ blocks << block
+ end
+
+ def execute_blocks
+ blocks.each do |block|
+ block.call(self)
+ end
+ @blocks = []
+ end
end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 1a282dbce4..c7e54e7b63 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -89,6 +89,19 @@ class RichPerson < ActiveRecord::Base
self.table_name = 'people'
has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+
+ before_validation :run_before_create, on: :create
+ before_validation :run_before_validation
+
+ private
+
+ def run_before_create
+ self.first_name = first_name.to_s + 'run_before_create'
+ end
+
+ def run_before_validation
+ self.first_name = first_name.to_s + 'run_before_validation'
+ end
end
class NestedPerson < ActiveRecord::Base
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 170fc2ffe3..e472cf951d 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -13,11 +13,11 @@ class Pirate < ActiveRecord::Base
:after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}"},
:before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"},
:after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"}
+ has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true
has_many :treasures, :as => :looter
has_many :treasure_estimates, :through => :treasures, :source => :price_estimates
- # These both have :autosave enabled because accepts_nested_attributes_for is used on them.
has_one :ship
has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship'
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index faf539a562..6399a68d95 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -40,6 +40,8 @@ class Post < ActiveRecord::Base
scope :with_comments, -> { preload(:comments) }
scope :with_tags, -> { preload(:taggings) }
+ scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) }
+
has_many :comments do
def find_most_recent
order("id DESC").first
@@ -145,10 +147,18 @@ class Post < ActiveRecord::Base
has_many :lazy_readers
has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, :class_name => 'LazyReader'
+ has_many :lazy_people, :through => :lazy_readers, :source => :person
+ has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, :class_name => 'LazyReader'
+ has_many :lazy_people_unscope_skimmers, :through => :lazy_readers_unscope_skimmers, :source => :person
+
def self.top(limit)
ranked_by_comments.limit_by(limit)
end
+ def self.written_by(author)
+ where(id: author.posts.pluck(:id))
+ end
+
def self.reset_log
@log = []
end
diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb
index 3a6b7fad34..91afc1898c 100644
--- a/activerecord/test/models/reader.rb
+++ b/activerecord/test/models/reader.rb
@@ -16,6 +16,8 @@ class LazyReader < ActiveRecord::Base
self.table_name = "readers"
default_scope -> { where(skimmer: true) }
+ scope :skimmers_or_not, -> { unscope(:where => :skimmer) }
+
belongs_to :post
belongs_to :person
end
diff --git a/activerecord/test/models/record.rb b/activerecord/test/models/record.rb
new file mode 100644
index 0000000000..f77ac9fc03
--- /dev/null
+++ b/activerecord/test/models/record.rb
@@ -0,0 +1,2 @@
+class Record < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/student.rb b/activerecord/test/models/student.rb
index f459f2a9a3..28a0b6c99b 100644
--- a/activerecord/test/models/student.rb
+++ b/activerecord/test/models/student.rb
@@ -1,3 +1,4 @@
class Student < ActiveRecord::Base
has_and_belongs_to_many :lessons
+ belongs_to :college
end
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index a581b381e8..80d4725f7e 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -3,5 +3,5 @@ class Tag < ActiveRecord::Base
has_many :taggables, :through => :taggings
has_one :tagging
- has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post'
-end \ No newline at end of file
+ has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post'
+end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index e864295acf..a69d3fd3df 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -3,6 +3,7 @@ class Treasure < ActiveRecord::Base
belongs_to :looter, :polymorphic => true
has_many :price_estimates, :as => :estimate_of
+ has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false
accepts_nested_attributes_for :looter
end
diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb
new file mode 100644
index 0000000000..a3d0962ad6
--- /dev/null
+++ b/activerecord/test/models/uuid_child.rb
@@ -0,0 +1,3 @@
+class UuidChild < ActiveRecord::Base
+ belongs_to :uuid_parent
+end
diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb
new file mode 100644
index 0000000000..5634f22d0c
--- /dev/null
+++ b/activerecord/test/models/uuid_parent.rb
@@ -0,0 +1,3 @@
+class UuidParent < ActiveRecord::Base
+ has_many :uuid_children
+end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index a86a188bcf..4fcbf4dbd2 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -99,6 +99,15 @@ _SQL
_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
+
if 't' == select_value("select 'json'=ANY(select typname from pg_type)")
execute <<_SQL
CREATE TABLE postgresql_json_data_type (
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9a7d918a25..da3074e90f 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -17,6 +17,15 @@ ActiveRecord::Schema.define do
ActiveRecord::Base.connection.create_table(*args, &block)
ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000"
end
+ when "PostgreSQL"
+ enable_uuid_ossp!(ActiveRecord::Base.connection)
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
+ end
end
@@ -97,6 +106,7 @@ ActiveRecord::Schema.define do
t.column :name, :string
t.column :status, :integer, default: 0
t.column :read_status, :integer, default: 0
+ t.column :nullable_status, :integer
end
create_table :booleans, force: true do |t|
@@ -160,6 +170,10 @@ ActiveRecord::Schema.define do
t.integer :references, null: false
end
+ create_table :columns, force: true do |t|
+ t.references :record
+ end
+
create_table :comments, force: true do |t|
t.integer :post_id, null: false
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
@@ -197,6 +211,7 @@ ActiveRecord::Schema.define do
end
create_table :computers, force: true do |t|
+ t.string :system
t.integer :developer, null: false
t.integer :extendedWarranty, null: false
end
@@ -636,6 +651,8 @@ ActiveRecord::Schema.define do
create_table :students, force: true do |t|
t.string :name
+ t.boolean :active
+ t.integer :college_id
end
create_table :subscribers, force: true, id: false do |t|
@@ -669,10 +686,14 @@ ActiveRecord::Schema.define do
end
create_table :topics, force: true do |t|
- t.string :title
+ t.string :title, limit: 250
t.string :author_name
t.string :author_email_address
- t.datetime :written_on
+ if mysql_56?
+ t.datetime :written_on, limit: 6
+ else
+ t.datetime :written_on
+ end
t.time :bonus_time
t.date :last_read
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
@@ -811,6 +832,8 @@ ActiveRecord::Schema.define do
t.integer :department_id
end
+ create_table :records, force: true do |t|
+ end
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
diff --git a/activerecord/test/support/connection_helper.rb b/activerecord/test/support/connection_helper.rb
new file mode 100644
index 0000000000..4a19e5df44
--- /dev/null
+++ b/activerecord/test/support/connection_helper.rb
@@ -0,0 +1,14 @@
+module ConnectionHelper
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+
+ # Used to drop all cache query plans in tests.
+ def reset_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+end
diff --git a/activerecord/test/support/ddl_helper.rb b/activerecord/test/support/ddl_helper.rb
new file mode 100644
index 0000000000..0107babaaf
--- /dev/null
+++ b/activerecord/test/support/ddl_helper.rb
@@ -0,0 +1,8 @@
+module DdlHelper
+ def with_example_table(connection, table_name, definition = nil)
+ connection.exec_query("CREATE TABLE #{table_name}(#{definition})")
+ yield
+ ensure
+ connection.exec_query("DROP TABLE #{table_name}")
+ end
+end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index adaeb99c87..5e430d20fa 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,531 +1,95 @@
-* Added `Hash#compact` and `Hash#compact!` for removing items with nil value from hash.
+* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable
+ UUID fixtures on PostgreSQL.
- *Celestino Gomes*
+ *Roderick van Domburg*
-* Maintain proleptic gregorian in Time#advance
+* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is
+ true.
- `Time#advance` uses `Time#to_date` and `Date#advance` to calculate a new date.
- The `Date` object returned by `Time#to_date` is constructed with the assumption
- that the `Time` object represents a proleptic gregorian date, but it is
- configured to observe the default julian calendar reform date (2299161j)
- for purposes of calculating month, date and year:
+ This fixes the current situation of:
- Time.new(1582, 10, 4).to_date.to_s # => "1582-09-24"
- Time.new(1582, 10, 4).to_date.gregorian.to_s # => "1582-10-04"
+ 1.second.eql?(1.second) #=> false
- This patch ensures that when the intermediate `Date` object is advanced
- to yield a new `Date` object, that the `Time` object for return is constructed
- with a proleptic gregorian month, date and year.
+ `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:
- *Riley Lynch*
+ 1.eql?(1.0) #=> false
+ 1.0.eql?(1) #=> false
-* `MemCacheStore` should only accept a `Dalli::Client`, or create one.
+ 1.second.eql?(1) #=> false (was true)
+ 1.eql?(1.second) #=> false
- *arthurnn*
+ { 1 => "foo", 1.0 => "bar" }
+ #=> { 1 => "foo", 1.0 => "bar" }
-* Don't lazy load the `tzinfo` library as it causes problems on Windows.
+ { 1 => "foo", 1.second => "bar" }
+ # now => { 1 => "foo", 1.second => "bar" }
+ # was => { 1 => "bar" }
- Fixes #13553.
+ And though the behavior of these hasn't changed, for reference:
- *Andrew White*
+ 1 == 1.0 #=> true
+ 1.0 == 1 #=> true
-* Use `remove_possible_method` instead of `remove_method` to avoid
- a `NameError` to be thrown on FreeBSD with the `Date` object.
+ 1 == 1.second #=> true
+ 1.second == 1 #=> true
- *Rafael Mendonça França*, *Robin Dupret*
+ *Emily Dobervich*
-* `blank?` and `present?` commit to return singletons.
+* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies
+ instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is
+ deprecated.
- *Xavier Noria*, *Pavel Pravosud*
+ *Pavel Pravosud*
-* Fixed Float related error in NumberHelper with large precisions.
+* `HashWithIndifferentAccess` better respects `#to_hash` on objects it's
+ given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept
+ objects which respond to `#to_hash`, even if those objects are not Hashes
+ directly.
- Before:
+ *Peter Jaros*
- ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
- #=> "3.14158999999999988261834005243144929409027099609375"
+* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead.
- After:
+ *Akshay Vishnoi*
- ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
- #=> "3.14159000000000000000000000000000000000000000000000"
+* Ensure classes which `include Enumerable` get `#to_json` in addition to
+ `#as_json`.
- *Kenta Murata*, *Akira Matsuda*
+ *Sammy Larbi*
-* Default the new `I18n.enforce_available_locales` config to `true`, meaning
- `I18n` will make sure that all locales passed to it must be declared in the
- `available_locales` list.
+* Change the signature of `fetch_multi` to return a hash rather than an
+ array. This makes it consistent with the output of `read_multi`.
- To disable it add the following configuration to your application:
+ *Parker Selbert*
- config.i18n.enforce_available_locales = false
+* 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.
- This also ensures I18n configuration is properly initialized taking the new
- option into account, to avoid their deprecations while booting up the app.
-
- *Carlos Antonio da Silva*, *Yves Senn*
-
-* Introduce Module#concerning: a natural, low-ceremony way to separate
- responsibilities within a class.
-
- Imported from https://github.com/37signals/concerning#readme
-
- class Todo < ActiveRecord::Base
- concerning :EventTracking do
- included do
- has_many :events
- end
-
- def latest_event
- ...
- end
-
- private
- def some_internal_method
- ...
- end
+ # app/models/concerns/authentication.rb
+ concern :Authentication do
+ included do
+ after_create :generate_private_key
end
- concerning :Trashable do
- def trashed?
- ...
- end
-
- def latest_event
- super some_option: true
+ class_methods do
+ def authenticate(credentials)
+ # ...
end
end
- end
-
- is equivalent to defining these modules inline, extending them into
- concerns, then mixing them in to the class.
-
- Inline concerns tame "junk drawer" classes that intersperse many unrelated
- class-level declarations, public instance methods, and private
- implementation. Coalesce related bits and give them definition.
- These are a stepping stone toward future growth & refactoring.
-
- When to move on from an inline concern:
- * Encapsulating state? Extract collaborator object.
- * Encompassing more public behavior or implementation? Move to separate file.
- * Sharing behavior among classes? Move to separate file.
-
- *Jeremy Kemper*
-
-* Fix file descriptor being leaked on each call to `Kernel.silence_stream`.
-
- *Mario Visic*
-
-* Added `Date#all_week/month/quarter/year` for generating date ranges.
-
- *Dmitriy Meremyanin*
-
-* Add `Time.zone.yesterday` and `Time.zone.tomorrow`. These follow the
- behavior of Ruby's `Date.yesterday` and `Date.tomorrow` but return localized
- versions, similar to how `Time.zone.today` has returned a localized version
- of `Date.today`.
-
- *Colin Bartlett*
-
-* Show valid keys when `assert_valid_keys` raises an exception, and show the
- wrong value as it was entered.
-
- *Gonzalo Rodríguez-Baltanás Díaz*
-
-* Both `cattr_*` and `mattr_*` method definitions now live in `active_support/core_ext/module/attribute_accessors`.
-
- Requires to `active_support/core_ext/class/attribute_accessors` are
- deprecated and will be removed in Ruby on Rails 4.2.
-
- *Genadi Samokovarov*
-
-* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to explicitly
- convert the value into an AS::Duration, i.e. `5.ago` => `5.seconds.ago`
-
- This will help to catch subtle bugs like:
-
- def recent?(days = 3)
- self.created_at >= days.ago
- end
-
- The above code would check if the model is created within the last 3 **seconds**.
-
- In the future, `Numeric#{ago,until,since,from_now}` should be removed completely,
- or throw some sort of errors to indicate there are no implicit conversion from
- Numeric to AS::Duration.
-
- *Godfrey Chan*
-
-* Requires JSON gem version 1.7.7 or above due to a security issue in older versions.
-
- *Godfrey Chan*
-
-* Removed the old pure-Ruby JSON encoder and switched to a new encoder based on the built-in JSON
- gem.
-
- Support for encoding `BigDecimal` as a JSON number, as well as defining custom `encode_json`
- methods to control the JSON output has been **removed from core**. The new encoder will always
- encode BigDecimals as `String`s and ignore any custom `encode_json` methods.
-
- The old encoder has been extracted into the `activesupport-json_encoder` gem. Installing that
- gem will bring back the ability to encode `BigDecimal`s as numbers as well as `encode_json`
- support.
-
- Setting the related configuration `ActiveSupport.encode_big_decimal_as_string` without the
- `activesupport-json_encoder` gem installed will raise an error.
-
- *Godfrey Chan*
-
-* Add `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These methods change current
- time to the given time or time difference by stubbing `Time.now` and `Date.today` to return the
- time or date after the difference calculation, or the time or date that got passed into the
- method respectively.
-
- Example for `#travel`:
-
- Time.now # => 2013-11-09 15:34:49 -05:00
- travel 1.day
- Time.now # => 2013-11-10 15:34:49 -05:00
- Date.today # => Sun, 10 Nov 2013
-
- Example for `#travel_to`:
-
- Time.now # => 2013-11-09 15:34:49 -05:00
- travel_to Time.new(2004, 11, 24, 01, 04, 44)
- Time.now # => 2004-11-24 01:04:44 -05:00
- Date.today # => Wed, 24 Nov 2004
-
- Both of these methods also accept a block, which will return the current time back to its
- original state at the end of the block:
-
- Time.now # => 2013-11-09 15:34:49 -05:00
-
- travel 1.day do
- User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
- end
-
- travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- User.create.created_at # => Wed, 24 Nov 2004 01:04:44 EST -05:00
- end
-
- Time.now # => 2013-11-09 15:34:49 -05:00
-
- This module is included in `ActiveSupport::TestCase` automatically.
-
- *Prem Sichanugrist*, *DHH*
-
-* Unify `cattr_*` interface: allow to pass a block to `cattr_reader`.
-
- Example:
-
- class A
- cattr_reader(:defr) { 'default_reader_value' }
- end
- A.defr # => 'default_reader_value'
-
- *Alexey Chernenkov*
-
-* Improved compatibility with the stdlib JSON gem.
-
- Previously, calling `::JSON.{generate,dump}` sometimes causes unexpected
- failures such as intridea/multi_json#86.
-
- `::JSON.{generate,dump}` now bypasses the ActiveSupport JSON encoder
- completely and yields the same result with or without ActiveSupport. This
- means that it will **not** call `as_json` and will ignore any options that
- the JSON gem does not natively understand. To invoke ActiveSupport's JSON
- encoder instead, use `obj.to_json(options)` or
- `ActiveSupport::JSON.encode(obj, options)`.
-
- *Godfrey Chan*
-
-* Fix Active Support `Time#to_json` and `DateTime#to_json` to return 3 decimal
- places worth of fractional seconds, similar to `TimeWithZone`.
-
- *Ryan Glover*
-
-* Removed circular reference protection in JSON encoder, deprecated
- `ActiveSupport::JSON::Encoding::CircularReferenceError`.
-
- *Godfrey Chan*, *Sergio Campamá*
-
-* Add `capitalize` option to `Inflector.humanize`, so strings can be humanized without being capitalized:
-
- 'employee_salary'.humanize # => "Employee salary"
- 'employee_salary'.humanize(capitalize: false) # => "employee salary"
-
- *claudiob*
-
-* Fixed `Object#as_json` and `Struct#as_json` not working properly with options. They now take
- the same options as `Hash#as_json`:
-
- struct = Struct.new(:foo, :bar).new
- struct.foo = "hello"
- struct.bar = "world"
- json = struct.as_json(only: [:foo]) # => {foo: "hello"}
-
- *Sergio Campamá*, *Godfrey Chan*
-
-* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like `getTime()`.
-
- *DHH*
-
-* Calling `ActiveSupport::JSON.decode` with unsupported options now raises an error.
-
- *Godfrey Chan*
-
-* Support `:unless_exist` in `FileStore`.
-
- *Michael Grosser*
-
-* Fix `slice!` deleting the default value of the hash.
-
- *Antonio Santos*
-
-* `require_dependency` accepts objects that respond to `to_path`, in
- particular `Pathname` instances.
-
- *Benjamin Fleischer*
-
-* Disable the ability to iterate over Range of AS::TimeWithZone
- due to significant performance issues.
-
- *Bogdan Gusiev*
-
-* Allow attaching event subscribers to ActiveSupport::Notifications namespaces
- before they're defined. Essentially, this means instead of this:
-
- class JokeSubscriber < ActiveSupport::Subscriber
- def sql(event)
- puts "A rabbi and a priest walk into a bar..."
- end
-
- # This call needs to happen *after* defining the methods.
- attach_to "active_record"
- end
-
- You can do this:
- class JokeSubscriber < ActiveSupport::Subscriber
- # This is much easier to read!
- attach_to "active_record"
-
- def sql(event)
- puts "A rabbi and a priest walk into a bar..."
+ def generate_private_key
+ # ...
end
end
- This should make it easier to read and understand these subscribers.
-
- *Daniel Schierbeck*
-
-* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods.
-
- Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases.
-
- *Anatoli Makarevich*
-
-* Fix ActiveSupport::Cache::FileStore#cleanup to no longer rely on missing each_key method.
-
- *Murray Steele*
-
-* Ensure that autoloaded constants in all-caps nestings are marked as
- autoloaded.
-
- *Simon Coffey*
-
-* Add `String#remove(pattern)` as a short-hand for the common pattern of
- `String#gsub(pattern, '')`.
-
- *DHH*
-
-* Adds a new deprecation behaviour that raises an exception. Throwing this
- line into +config/environments/development.rb+
-
- ActiveSupport::Deprecation.behavior = :raise
-
- will cause the application to raise an +ActiveSupport::DeprecationException+
- on deprecations.
-
- Use this for aggressive deprecation cleanups.
-
- *Xavier Noria*
-
-* Remove 'cow' => 'kine' irregular inflection from default inflections.
-
- *Andrew White*
-
-* Add `DateTime#to_s(:iso8601)` and `Date#to_s(:iso8601)` for consistency.
-
- *Andrew White*
-
-* Add `Time#to_s(:iso8601)` for easy conversion of times to the iso8601 format for easy Javascript date parsing.
-
- *DHH*
-
-* Improve `ActiveSupport::Cache::MemoryStore` cache size calculation.
- The memory used by a key/entry pair is calculated via `#cached_size`:
-
- def cached_size(key, entry)
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
- end
-
- The value of `PER_ENTRY_OVERHEAD` is 240 bytes based on an [empirical
- estimation](https://gist.github.com/ssimeonov/6047200) for 64-bit MRI on
- 1.9.3 and 2.0.
-
- Fixes #11512.
-
- *Simeon Simeonov*
-
-* Only raise `Module::DelegationError` if it's the source of the exception.
-
- Fixes #10559.
-
- *Andrew White*
-
-* Make `Time.at_with_coercion` retain the second fraction and return local time.
-
- Fixes #11350.
-
- *Neer Friedman*, *Andrew White*
-
-* Make `HashWithIndifferentAccess#select` always return the hash, even when
- `Hash#select!` returns `nil`, to allow further chaining.
-
- *Marc Schütz*
-
-* Remove deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`).
-
- *Arun Agrawal*
-
-* Remove deprecated `Module#local_constant_names` in favor of `Module#local_constants`.
-
- *Arun Agrawal*
-
-* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_format`.
-
- *Arun Agrawal*
-
-* Remove deprecated `Logger` core extensions (`core_ext/logger.rb`).
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `Time#time_with_datetime_fallback`, `Time#utc_time`
- and `Time#local_time` in favor of `Time#utc` and `Time#local`.
-
- *Vipul A M*
-
-* Remove deprecated `Hash#diff` with no replacement.
-
- If you're using it to compare hashes for the purpose of testing, please use
- MiniTest's `assert_equal` instead.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`.
-
- *Vipul A M*
-
-* Remove deprecated `Proc#bind` with no replacement.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `Array#uniq_by` and `Array#uniq_by!`, use native
- `Array#uniq` and `Array#uniq!` instead.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `ActiveSupport::BasicObject`, use `ActiveSupport::ProxyObject` instead.
-
- *Carlos Antonio da Silva*
-
-* Remove deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
-
- *Yves Senn*
-
-* Remove deprecated `assert_present` and `assert_blank` methods, use `assert
- object.blank?` and `assert object.present?` instead.
-
- *Yves Senn*
-
-* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured
- with multiple silencers.
-
- Fixes #11030.
-
- *Mark J. Titorenko*
-
-* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess`
- instance instead of a `Hash` instance.
-
- Fixes #10723.
-
- *Albert Llop*
-
-* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps
- sub-second resolution when wrapping a `DateTime` value.
-
- Fixes #10855.
-
- *Andrew White*
-
-* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling
- `#blame_file!` on Exceptions that do not have the Blamable mixin
-
- *Andrew Kreiling*
-
-* Override `Time.at` to support the passing of Time-like values when called with a single argument.
-
- *Andrew White*
-
-* Prevent side effects to hashes inside arrays when
- `Hash#with_indifferent_access` is called.
-
- Fixes #10526.
-
- *Yves Senn*
-
-* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement.
-
- *Toshinori Kajihara*
-
-* Raise an error when multiple `included` blocks are defined for a Concern.
- The old behavior would silently discard previously defined blocks, running
- only the last one.
-
- *Mike Dillon*
-
-* Replace `multi_json` with `json`.
-
- Since Rails requires Ruby 1.9 and since Ruby 1.9 includes `json` in the standard library,
- `multi_json` is no longer necessary.
-
- *Erik Michaels-Ober*
-
-* Added escaping of U+2028 and U+2029 inside the json encoder.
- These characters are legal in JSON but break the Javascript interpreter.
- After escaping them, the JSON is still legal and can be parsed by Javascript.
-
- *Mario Caropreso + Viktor Kelemen + zackham*
-
-* Fix skipping object callbacks using metadata fetched via callback chain
- inspection methods (`_*_callbacks`)
-
- *Sean Walbran*
-
-* Add a `fetch_multi` method to the cache stores. The method provides
- an easy to use API for fetching multiple values from the cache.
-
- Example:
-
- # Calculating scores is expensive, so we only do it for posts
- # that have been updated. Cache keys are automatically extracted
- # from objects that define a #cache_key method.
- scores = Rails.cache.fetch_multi(*posts) do |post|
- calculate_score(post)
+ # app/models/user.rb
+ class User < ActiveRecord::Base
+ include Authentication
end
- *Daniel Schierbeck*
+ *Jeremy Kemper*
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 53154aef27..a627fa8651 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -357,20 +357,19 @@ module ActiveSupport
#
# Options are passed to the underlying cache implementation.
#
- # Returns an array with the data for each of the names. For example:
+ # Returns a hash with the data for each of the names. For example:
#
# cache.write("bim", "bam")
- # cache.fetch_multi("bim", "boom") {|key| key * 2 }
- # # => ["bam", "boomboom"]
+ # cache.fetch_multi("bim", "boom") { |key| key * 2 }
+ # # => { "bam" => "bam", "boom" => "boomboom" }
#
def fetch_multi(*names)
options = names.extract_options!
options = merged_options(options)
-
results = read_multi(*names, options)
- names.map do |name|
- results.fetch(name) do
+ names.each_with_object({}) do |name, memo|
+ memo[name] = results.fetch(name) do
value = yield name
write(name, value, options)
value
@@ -452,7 +451,7 @@ module ActiveSupport
# Clear the entire cache. Be careful with this method since it could
# affect other processes if shared cache is being used.
#
- # Options are passed to the underlying cache implementation.
+ # The options hash is passed to the underlying cache implementation.
#
# All implementations may not support this method.
def clear(options = nil)
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 4eaf57f385..73c6b3cb88 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/string/inflections'
-require 'rack/body_proxy'
+require 'active_support/per_thread_registry'
module ActiveSupport
module Cache
@@ -9,6 +9,8 @@ module ActiveSupport
# duration of a block. Repeated calls to the cache for the same key will hit the
# in-memory cache for faster access.
module LocalCache
+ autoload :Middleware, 'active_support/cache/strategy/local_cache_middleware'
+
# Class for storing and registering the local caches.
class LocalCacheRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
@@ -64,37 +66,6 @@ module ActiveSupport
def with_local_cache
use_temporary_local_cache(LocalStore.new) { yield }
end
-
- #--
- # This class wraps up local storage for middlewares. Only the middleware method should
- # construct them.
- class Middleware # :nodoc:
- attr_reader :name, :local_cache_key
-
- def initialize(name, local_cache_key)
- @name = name
- @local_cache_key = local_cache_key
- @app = nil
- end
-
- def new(app)
- @app = app
- self
- end
-
- def call(env)
- LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) do
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- end
- response
- rescue Exception
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- raise
- end
- end
-
# Middleware class can be inserted as a Rack handler to be local cache for the
# duration of request.
def middleware
@@ -115,13 +86,13 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- increment_or_decrement(value, name, amount, options)
+ set_cache_value(value, name, amount, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
value = bypass_local_cache{super}
- increment_or_decrement(value, name, amount, options)
+ set_cache_value(value, name, amount, options)
value
end
@@ -149,8 +120,7 @@ module ActiveSupport
super
end
- private
- def increment_or_decrement(value, name, amount, options)
+ def set_cache_value(value, name, amount, options)
if local_cache
local_cache.mute do
if value
@@ -162,6 +132,8 @@ module ActiveSupport
end
end
+ private
+
def local_cache_key
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
new file mode 100644
index 0000000000..901c2e05a8
--- /dev/null
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -0,0 +1,39 @@
+require 'rack/body_proxy'
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+
+ #--
+ # This class wraps up local storage for middlewares. Only the middleware method should
+ # construct them.
+ class Middleware # :nodoc:
+ attr_reader :name, :local_cache_key
+
+ def initialize(name, local_cache_key)
+ @name = name
+ @local_cache_key = local_cache_key
+ @app = nil
+ end
+
+ def new(app)
+ @app = app
+ self
+ end
+
+ def call(env)
+ LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
+ response = @app.call(env)
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
+ end
+ response
+ rescue Exception
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil)
+ raise
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e14ece7f35..05ca943776 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -131,8 +131,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
lambda { |env|
target = env.target
@@ -149,6 +147,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback, halted_lambda, filter)
lambda { |env|
@@ -166,6 +165,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -178,6 +178,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -185,6 +186,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :simple
end
class After
@@ -208,8 +210,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions)
lambda { |env|
env = next_callback.call env
@@ -223,6 +223,7 @@ module ActiveSupport
env
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback)
lambda { |env|
@@ -233,6 +234,7 @@ module ActiveSupport
env
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -246,6 +248,7 @@ module ActiveSupport
env
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -254,6 +257,7 @@ module ActiveSupport
env
}
end
+ private_class_method :simple
end
class Around
@@ -269,8 +273,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions)
lambda { |env|
target = env.target
@@ -288,6 +290,7 @@ module ActiveSupport
end
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback)
lambda { |env|
@@ -305,6 +308,7 @@ module ActiveSupport
end
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -322,6 +326,7 @@ module ActiveSupport
end
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -332,6 +337,7 @@ module ActiveSupport
env
}
end
+ private_class_method :simple
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index b796d01dfd..9d5cee54e3 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -26,7 +26,7 @@ module ActiveSupport
# scope :disabled, -> { where(disabled: true) }
# end
#
- # module ClassMethods
+ # class_methods do
# ...
# end
# end
@@ -130,5 +130,13 @@ module ActiveSupport
super
end
end
+
+ def class_methods(&class_methods_module_definition)
+ mod = const_defined?(:ClassMethods) ?
+ const_get(:ClassMethods) :
+ const_set(:ClassMethods, Module.new)
+
+ mod.module_eval(&class_methods_module_definition)
+ end
end
end
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 39b8cea807..843c592669 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,22 +1,7 @@
require 'bigdecimal'
require 'bigdecimal/util'
-require 'yaml'
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
-
- # Backport this method if it doesn't exist
- unless method_defined?(:to_d)
- def to_d
- self
- end
- end
-
DEFAULT_STRING_FORMAT = 'F'
def to_formatted_s(*args)
if args[0].is_a?(Symbol)
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
new file mode 100644
index 0000000000..46ba93ead4
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
@@ -0,0 +1,14 @@
+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_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 083b165dce..84d5e95e7a 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,6 +1,4 @@
-require 'active_support/deprecation'
+# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d,
+# but we keep this around for libraries that directly require it knowing they
+# want cattr_*. No need to deprecate.
require 'active_support/core_ext/module/attribute_accessors'
-
-ActiveSupport::Deprecation.warn(
- "The cattr_* method definitions have been moved into active_support/core_ext/module/attribute_accessors. Please require that instead."
-)
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index c2219beb5a..1c305c5970 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/module/deprecation'
+
class Class
def superclass_delegating_accessor(name, options = {})
@@ -21,6 +23,8 @@ class Class
end
end
+ deprecate superclass_delegating_accessor: :class_attribute
+
private
# Take the object being set and store it in a method. This gives us automatic
# inheritance behavior, without having to store the object in an instance
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 4501b7ff58..1343beb87a 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -35,7 +35,7 @@ module Enumerable
if block_given?
Hash[map { |elem| [yield(elem), elem] }]
else
- to_enum :index_by
+ to_enum(:index_by) { size if respond_to?(:size) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 7bea461c77..6c3e48a3ca 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -105,7 +105,7 @@ class Hash
# hash = Hash.from_xml(xml)
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
#
- # DisallowedType is raised if the XML contains attributes with <tt>type="yaml"</tt> or
+ # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
# <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
@@ -241,4 +241,3 @@ module ActiveSupport
end
end
-
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 0275f4c037..293a3b2619 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,4 +1,5 @@
-require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/agnostics'
-require 'active_support/core_ext/kernel/debugger'
+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/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
new file mode 100644
index 0000000000..bf72caa058
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -0,0 +1,10 @@
+require 'active_support/core_ext/module/concerning'
+
+module Kernel
+ # A shortcut to define a toplevel concern, not within a module.
+ #
+ # See Module::Concerning for more.
+ def concern(topic, &module_definition)
+ Object.concern topic, &module_definition
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 3b5e205244..f3f8416905 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -41,6 +41,8 @@ module Kernel
# 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')
@@ -100,6 +102,8 @@ module Kernel
# Silences both STDOUT and STDERR, even for subprocesses.
#
# quietly { system 'bundle install' }
+ #
+ # This method is not thread-safe.
def quietly
silence_stream(STDOUT) do
silence_stream(STDERR) do
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index fe24f3716d..768b980f21 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -7,6 +7,7 @@ class LoadError
]
unless method_defined?(:path)
+ # Returns the path which was unable to be loaded.
def path
@path ||= begin
REGEXPS.find do |regex|
@@ -17,9 +18,11 @@ class LoadError
end
end
+ # Returns true if the given path name (except perhaps for the ".rb"
+ # extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
end
end
-MissingSourceFile = LoadError \ No newline at end of file
+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 db07d549b0..67f0e0335d 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -27,7 +27,8 @@ class Module
def attr_internal_define(attr_name, type)
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
- class_eval do # class_eval is necessary on 1.9 or else the methods a made private
+ # 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
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index b22dc5ff1e..07a392404e 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -63,7 +63,7 @@ class Module
#
# == Mix-in noise exiled to its own file:
#
- # Once our chunk of behavior starts pushing the scroll-to-understand it
+ # Once our chunk of behavior starts pushing the scroll-to-understand it's
# boundary, we give in and move it to a separate file. At this size, the
# overhead feels in good proportion to the size of our extraction, despite
# diluting our at-a-glance sense of how things really work.
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 58146cdf7a..f855833a24 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -178,30 +178,32 @@ class Module
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
if allow_nil
- module_eval(<<-EOS, file, line - 3)
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- _ = #{to} # _ = client
- if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
- _.#{method}(#{definition}) # _.name(*args, &block)
- end # end
- end # end
- EOS
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
+ "_ = #{to}", # _ = client
+ "if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name)
+ " _.#{method}(#{definition})", # _.name(*args, &block)
+ "end", # end
+ "end" # end
+ ].join ';'
else
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- module_eval(<<-EOS, file, line - 2)
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
- _ = #{to} # _ = client
- _.#{method}(#{definition}) # _.name(*args, &block)
- rescue NoMethodError => e # rescue NoMethodError => e
- if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name
- #{exception} # # add helpful message to the exception
- else # else
- raise # raise
- end # end
- end # end
- EOS
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
+ " _ = #{to}", # _ = client
+ " _.#{method}(#{definition})", # _.name(*args, &block)
+ "rescue NoMethodError => e", # rescue NoMethodError => e
+ " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name
+ " #{exception}", # # add helpful message to the exception
+ " else", # else
+ " raise", # raise
+ " end", # end
+ "end" # end
+ ].join ';'
end
+
+ module_eval(method_def, file, line)
end
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 9cd7485e2e..3d2c809c9f 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -78,6 +78,9 @@ 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
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index b5671f66d0..55f281b213 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -12,4 +12,16 @@ class Object
rescue NoMethodError
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
end
+
+ # Returns the receiver if it's included in the argument otherwise returns +nil+.
+ # Argument must be any object which responds to +#include?+. Usage:
+ #
+ # params[:bucket_type].presence_in %w( project calendar )
+ #
+ # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ #
+ # @return [Object]
+ def presence_in(another_object)
+ self.in?(another_object) ? self : nil
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 1675145ffe..5496692373 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -16,17 +16,17 @@ require 'active_support/core_ext/module/aliasing'
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-#
+#
# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the
# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always
# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the
# calls to the original to_json method.
-#
+#
# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
# should give exactly the same results with or without active support.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass|
klass.class_eval do
def to_json_with_active_support_encoder(options = nil)
if options.is_a?(::JSON::State)
@@ -163,7 +163,7 @@ end
class Time
def as_json(options = nil) #:nodoc:
if ActiveSupport.use_standard_json_time_format
- xmlschema(3)
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
%(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
end
@@ -183,7 +183,7 @@ end
class DateTime
def as_json(options = nil) #:nodoc:
if ActiveSupport.use_standard_json_time_format
- xmlschema(3)
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
strftime('%Y/%m/%d %H:%M:%S %z')
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
index 3dcae6fc7f..f58364f9c6 100644
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -2,4 +2,4 @@ ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/obje
'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \
'instead.'
-require 'active_support/core_ext/object/json'
+require 'active_support/core_ext/object/json' \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 3b137ce6ae..13be0038c2 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -51,8 +51,12 @@ class Hash
#
# This method is also aliased as +to_query+.
def to_param(namespace = nil)
- collect do |key, value|
- value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end.sort! * '&'
+ if empty?
+ namespace ? nil.to_query(namespace) : ''
+ else
+ collect do |key, value|
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
+ end.sort! * '&'
+ 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 5d5fcf00e0..37352fa608 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -18,7 +18,12 @@ class Array
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
prefix = "#{key}[]"
- collect { |value| value.to_query(prefix) }.join '&'
+
+ if empty?
+ nil.to_query(prefix)
+ else
+ collect { |value| value.to_query(prefix) }.join '&'
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
new file mode 100644
index 0000000000..fec8f7c0ec
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -0,0 +1,47 @@
+module SecureRandom
+ UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+
+ # Generates a v5 non-random UUID (Universally Unique IDentifier).
+ #
+ # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
+ # ::uuid_from_hash always generates the same UUID for a given name and namespace combination.
+ #
+ # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
+ def self.uuid_from_hash(hash_class, uuid_namespace, name)
+ if hash_class == Digest::MD5
+ version = 3
+ elsif hash_class == Digest::SHA1
+ version = 5
+ else
+ raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
+ end
+
+ hash = hash_class.new
+ hash.update(uuid_namespace)
+ hash.update(name)
+
+ ary = hash.digest.unpack('NnnnnN')
+ ary[2] = (ary[2] & 0x0FFF) | (version << 12)
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
+
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
+ end
+
+ # Convenience method for ::uuid_from_hash using Digest::MD5.
+ def self.uuid_v3(uuid_namespace, name)
+ self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
+ end
+
+ # Convenience method for ::uuid_from_hash using Digest::SHA1.
+ def self.uuid_v5(uuid_namespace, name)
+ self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
+ end
+
+ class << self
+ # Alias for ::uuid.
+ alias_method :uuid_v4, :uuid
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 6018fd9641..ebd0dd3fc7 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -64,7 +64,7 @@ class String
# Returns the first character. If a limit is supplied, returns a substring
# from the beginning of the string until it reaches the limit value. If the
- # given limit is greater than or equal to the string length, returns self.
+ # given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
# str.first # => "h"
@@ -76,7 +76,7 @@ class String
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
to(limit - 1)
end
@@ -84,7 +84,7 @@ class String
# Returns the last character of the string. If a limit is supplied, returns a substring
# from the end of the string until it reaches the limit value (counting backwards). If
- # the given limit is greater than or equal to the string length, returns self.
+ # the given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
# str.last # => "o"
@@ -96,7 +96,7 @@ class String
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
from(-limit)
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cf9b1a4ec0..a943752f17 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -31,7 +31,7 @@ class String
def pluralize(count = nil, locale = :en)
locale = count if count.is_a?(Symbol)
if count == 1
- self
+ self.dup
else
ActiveSupport::Inflector.pluralize(self, locale)
end
@@ -130,6 +130,8 @@ class String
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ''
#
# See also +deconstantize+.
def demodulize
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 eb02b6a442..2c8995be9a 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,5 +1,6 @@
require 'erb'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/deprecation'
class ERB
module Util
@@ -124,7 +125,7 @@ module ActiveSupport #:nodoc:
class SafeBuffer < String
UNSAFE_STRING_METHODS = %w(
capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
- slice squeeze strip sub succ swapcase tr tr_s upcase prepend
+ slice squeeze strip sub succ swapcase tr tr_s upcase
)
alias_method :original_concat, :concat
@@ -169,15 +170,18 @@ module ActiveSupport #:nodoc:
self[0, 0]
end
- def concat(value)
- if !html_safe? || value.html_safe?
- super(value)
- else
- super(ERB::Util.h(value))
+ %w[concat prepend].each do |method_name|
+ define_method method_name do |value|
+ super(html_escape_interpolated_argument(value))
end
end
alias << concat
+ def prepend!(value)
+ ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
+ prepend value
+ end
+
def +(other)
dup.concat(other)
end
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
index ac1ffa4128..4cd6634558 100644
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -62,6 +62,13 @@ class Thread
_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
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 9fd26156c7..dbf1f2f373 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -20,7 +20,7 @@ class Time
:iso8601 => lambda { |time| time.iso8601 }
}
- # Converts to a formatted string. See DATE_FORMATS for builtin formats.
+ # Converts to a formatted string. See DATE_FORMATS for built-in formats.
#
# This method is aliased to <tt>to_s</tt>.
#
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 6be19771f5..59675d744e 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -407,7 +407,8 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false
+ # to_s works around a ruby1.9 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
@@ -665,6 +666,14 @@ module ActiveSupport #:nodoc:
constants = normalized.split('::')
to_remove = constants.pop
+ # Remove the file path from the loaded list.
+ file_path = search_for_file(const.underscore)
+ if file_path
+ expanded = File.expand_path(file_path)
+ expanded.sub!(/\.rb\z/, '')
+ self.loaded.delete(expanded)
+ end
+
if constants.empty?
parent = Object
else
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 7df4857c25..09eb732ef7 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -49,6 +49,10 @@ module ActiveSupport
end
end
+ def eql?(other)
+ other.is_a?(Duration) && self == other
+ end
+
def self.===(other) #:nodoc:
other.is_a?(Duration)
rescue ::NoMethodError
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
new file mode 100644
index 0000000000..83a3bf7a5d
--- /dev/null
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveSupport
+ # Returns the version of the currently loaded ActiveSupport as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index f690eab604..a4ebdea598 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -55,9 +55,9 @@ module ActiveSupport
end
def initialize(constructor = {})
- if constructor.is_a?(Hash)
+ if constructor.respond_to?(:to_hash)
super()
- update(constructor)
+ update(constructor.to_hash)
else
super(constructor)
end
@@ -72,6 +72,7 @@ module ActiveSupport
end
def self.new_from_hash_copying_default(hash)
+ hash = hash.to_hash
new(hash).tap do |new_hash|
new_hash.default = hash.default
end
@@ -125,7 +126,7 @@ module ActiveSupport
if other_hash.is_a? HashWithIndifferentAccess
super(other_hash)
else
- other_hash.each_pair do |key, value|
+ other_hash.to_hash.each_pair do |key, value|
if block_given? && key?(key)
value = yield(convert_key(key), self[key], value)
end
@@ -159,7 +160,7 @@ module ActiveSupport
#
# counters.fetch('foo') # => 1
# counters.fetch(:bar, 0) # => 0
- # counters.fetch(:bar) {|key| 0} # => 0
+ # counters.fetch(:bar) { |key| 0 } # => 0
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
def fetch(key, *extras)
super(convert_key(key), *extras)
@@ -172,7 +173,7 @@ module ActiveSupport
# hash[:b] = 'y'
# hash.values_at('a', 'b') # => ["x", "y"]
def values_at(*indices)
- indices.collect {|key| self[convert_key(key)]}
+ indices.collect { |key| self[convert_key(key)] }
end
# Returns an exact copy of the hash.
@@ -228,7 +229,11 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
- dup.tap {|hash| hash.select!(*args, &block)}
+ dup.tap { |hash| hash.select!(*args, &block) }
+ end
+
+ def reject(*args, &block)
+ dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index 4ea6abfa12..2ca1124e76 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,5 +1,11 @@
require 'active_support/inflector/inflections'
+#--
+# Defines the standard inflection rules. These are the starting point for
+# new projects and are not considered complete. The current set of inflection
+# rules is frozen. This means, we do not change them to become more complete.
+# This is a safety measure to keep existing applications from breaking.
+#++
module ActiveSupport
Inflector.inflections(:en) do |inflect|
inflect.plural(/$/, 's')
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index cdee4c2ca5..69f77453e7 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -89,6 +89,7 @@ module ActiveSupport
#
# 'SSLError'.underscore.camelize # => "SslError"
def underscore(camel_cased_word)
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::', '/')
word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
@@ -117,7 +118,7 @@ module ActiveSupport
result.gsub!(/([a-z\d]*)/i) { |match|
"#{inflections.acronyms[match] || match.downcase}"
}
- result.gsub!(/^\w/) { $&.upcase } if options.fetch(:capitalize, true)
+ result.gsub!(/^\w/) { |match| match.upcase } if options.fetch(:capitalize, true)
result
end
@@ -154,7 +155,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # 'business'.classify # => "Busines"
+ # 'calculus'.classify # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -171,6 +172,8 @@ module ActiveSupport
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ""
#
# See also +deconstantize+.
def demodulize(path)
@@ -227,7 +230,7 @@ module ActiveSupport
def constantize(camel_cased_word)
names = camel_cased_word.split('::')
- # Trigger a builtin NameError exception including the ill-formed constant in the message.
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
# Remove the first blank element in case of '::ClassName' notation.
@@ -241,8 +244,8 @@ module ActiveSupport
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
- # Go down the ancestors to check it it's owned
- # directly before we reach Object or the end of ancestors.
+ # Go down the ancestors to check if it is owned directly. The check
+ # stops when we reach Object or the end of ancestors tree.
constant = constant.ancestors.inject do |const, ancestor|
break const if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 2859075e10..f29d42276d 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/delegation'
module ActiveSupport
class << self
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=,
@@ -60,7 +61,7 @@ module ActiveSupport
end
# Mark these as private so we don't leak encoding-specific constructs
- private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
+ private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
# Convert an object into a "JSON-ready" representation composed of
@@ -105,6 +106,10 @@ module ActiveSupport
# as a safety measure.
attr_accessor :escape_html_entities_in_json
+ # Sets the precision of encoded time values.
+ # Defaults to 3 (equivalent to millisecond precision)
+ attr_accessor :time_precision
+
# 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
@@ -161,6 +166,7 @@ module ActiveSupport
self.use_standard_json_time_format = true
self.escape_html_entities_in_json = true
self.json_encoder = JSONGemEncoder
+ self.time_precision = 3
end
end
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 598c46bce5..51d2da3a79 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -57,18 +57,16 @@ module ActiveSupport
# secret they've provided is at least 30 characters in length.
def ensure_secret_secure(secret)
if secret.blank?
- raise ArgumentError, "A secret is required to generate an " +
- "integrity hash for cookie session data. Use " +
- "config.secret_key_base = \"some secret phrase of at " +
- "least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/initializers/secret_token.rb"
+ raise ArgumentError, "A secret is required to generate an integrity hash " \
+ "for cookie session data. Set a secret_key_base of at least " \
+ "#{SECRET_MIN_LENGTH} characters in config/secrets.yml."
end
if secret.length < SECRET_MIN_LENGTH
- raise ArgumentError, "Secret should be something secure, " +
- "like \"#{SecureRandom.hex(16)}\". The value you " +
- "provided, \"#{secret}\", is shorter than the minimum length " +
- "of #{SECRET_MIN_LENGTH} characters"
+ raise ArgumentError, "Secret should be something secure, " \
+ "like \"#{SecureRandom.hex(16)}\". The value you " \
+ "provided, \"#{secret}\", is shorter than the minimum length " \
+ "of #{SECRET_MIN_LENGTH} characters."
end
end
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 7773611e11..b019ad0dec 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -12,7 +12,7 @@ module ActiveSupport
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
# where you don't want users to be able to determine the value of the payload.
#
- # salt = SecureRandom.random_bytes(64)
+ # salt = SecureRandom.random_bytes(64)
# key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 84799c2399..ea3cdcd024 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -213,7 +213,7 @@ module ActiveSupport
end
# Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
- if RUBY_VERSION >= '2.1'
+ if '<3'.respond_to?(:scrub)
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
# resulting in a valid UTF-8 string.
#
@@ -233,16 +233,16 @@ module ActiveSupport
# We're going to 'transcode' bytes from UTF-8 when possible, then fall back to
# CP1252 when we get errors. The final string will be 'converted' back to UTF-8
# before returning.
- reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE)
source = string.dup
- out = ''.force_encoding(Encoding::UTF_8_MAC)
+ out = ''.force_encoding(Encoding::UTF_16LE)
loop do
reader.primitive_convert(source, out)
_, _, _, error_bytes, _ = reader.primitive_errinfo
break if error_bytes.nil?
- out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
reader.finish
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index c42354fc83..c45f6cdcfa 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -32,8 +32,7 @@ module ActiveSupport
end
formatted_string =
- case rounded_number
- when BigDecimal
+ if BigDecimal === rounded_number && rounded_number.finite?
s = rounded_number.to_s('F') + '0'*precision
a, b = s.split('.', 2)
a + '.' + b[0, precision]
diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index e55ffd12c3..dea84e437f 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def method_missing(method, *arguments, &block)
- if arguments.last.is_a?(Proc)
+ if arguments.first.is_a?(Proc)
proc = arguments.pop
arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
else
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 1a3693f766..4680d5acb7 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -28,6 +28,14 @@ module ActiveSupport
coder.represent_seq '!omap', map { |k,v| { k => v } }
end
+ def select(*args, &block)
+ dup.tap { |hash| hash.select!(*args, &block) }
+ end
+
+ def reject(*args, &block)
+ dup.tap { |hash| hash.reject!(*args, &block) }
+ end
+
def nested_under_indifferent_access
self
end
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index c349bb5fb1..e709e6edf5 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -34,7 +34,7 @@ module ActiveSupport
# end
def test(name, &block)
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
- defined = instance_method(test_name) rescue false
+ defined = method_defined? test_name
raise "#{test_name} is already defined in #{self}" if defined
if block_given?
define_method(test_name, &block)
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 75ead48376..908af176be 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -37,6 +37,8 @@ module ActiveSupport
module Forking
def run_in_isolation(&blk)
read, write = IO.pipe
+ read.binmode
+ write.binmode
pid = fork do
read.close
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 94230e56ba..eefa84262e 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,10 +1,48 @@
module ActiveSupport
module Testing
+ class SimpleStubs # :nodoc:
+ Stub = Struct.new(:object, :method_name, :original_method)
+
+ def initialize
+ @stubs = {}
+ end
+
+ def stub_object(object, method_name, return_value)
+ key = [object.object_id, method_name]
+
+ if stub = @stubs[key]
+ unstub_object(stub)
+ end
+
+ new_name = "__simple_stub__#{method_name}"
+
+ @stubs[key] = Stub.new(object, method_name, new_name)
+
+ object.singleton_class.send :alias_method, new_name, method_name
+ object.define_singleton_method(method_name) { return_value }
+ end
+
+ def unstub_all!
+ @stubs.each_value do |stub|
+ unstub_object(stub)
+ end
+ @stubs = {}
+ end
+
+ private
+
+ def unstub_object(stub)
+ singleton_class = stub.object.singleton_class
+ singleton_class.send :undef_method, stub.method_name
+ singleton_class.send :alias_method, stub.method_name, stub.original_method
+ singleton_class.send :undef_method, stub.original_method
+ end
+ end
+
# Containing helpers that helps you test passage of time.
module TimeHelpers
- # Change current time to the time in the future or in the past by a given time difference by
- # stubbing +Time.now+ and +Date.today+. Note that the stubs are automatically removed
- # at the end of each test.
+ # Changes current time to the time in the future or in the past by a given time difference by
+ # stubbing +Time.now+ and +Date.today+.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel 1.day
@@ -23,33 +61,67 @@ module ActiveSupport
travel_to Time.now + duration, &block
end
- # Change current time to the given time by stubbing +Time.now+ and +Date.today+ to return the
- # time or date passed into this method. Note that the stubs are automatically removed
- # at the end of each test.
+ # Changes current time to the given time by stubbing +Time.now+ and
+ # +Date.today+ to return the time or date passed into this method.
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# Date.current # => Wed, 24 Nov 2004
#
+ # Dates are taken as their timestamp at the beginning of the day in the
+ # application time zone. <tt>Time.current</tt> returns said timestamp,
+ # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
+ # <tt>Date.current</tt> returns a date equal to the argument, and
+ # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
+ # be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
+ # or <tt>Date.today</tt>, in order to honor the application time zone
+ # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
+ #
# This method also accepts a block, which will return the current time back to its original
# state at the end of the block:
#
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- # User.create.created_at # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
# end
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
def travel_to(date_or_time, &block)
- Time.stubs now: date_or_time.to_time
- Date.stubs today: date_or_time.to_date
+ if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
+ now = date_or_time.midnight.to_time
+ else
+ now = date_or_time.to_time
+ end
+
+ simple_stubs.stub_object(Time, :now, now)
+ simple_stubs.stub_object(Date, :today, now.to_date)
if block_given?
- block.call
- Time.unstub :now
- Date.unstub :today
+ begin
+ block.call
+ ensure
+ travel_back
+ end
end
end
+
+ # Returns the current time back to its original state, by removing the stubs added by
+ # `travel` and `travel_to`.
+ #
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # travel_to Time.new(2004, 11, 24, 01, 04, 44)
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # travel_back
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ def travel_back
+ simple_stubs.unstub_all!
+ end
+
+ private
+
+ def simple_stubs
+ @simple_stubs ||= SimpleStubs.new
+ end
end
end
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 50db7da9d9..c25c97cfa8 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -45,7 +45,7 @@ module ActiveSupport
def initialize(utc_time, time_zone, local_time = nil, period = nil)
@utc, @time_zone, @time = utc_time, time_zone, local_time
- @period = @utc ? period : get_period_and_ensure_valid_local_time
+ @period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
# Returns a Time or DateTime instance that represents the time in +time_zone+.
@@ -132,8 +132,8 @@ module ActiveSupport
end
def xmlschema(fraction_digits = 0)
- fraction = if fraction_digits > 0
- (".%06i" % time.usec)[0, fraction_digits + 1]
+ fraction = if fraction_digits.to_i > 0
+ (".%06i" % time.usec)[0, fraction_digits.to_i + 1]
end
"#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
@@ -154,7 +154,7 @@ module ActiveSupport
# # => "2005/02/01 05:15:10 -1000"
def as_json(options = nil)
if ActiveSupport::JSON::Encoding.use_standard_json_time_format
- xmlschema(3)
+ xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
%(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
end
@@ -367,12 +367,12 @@ module ActiveSupport
end
private
- def get_period_and_ensure_valid_local_time
+ def get_period_and_ensure_valid_local_time(period)
# we don't want a Time.local instance enforcing its own DST rules as well,
# so transfer time values to a utc constructor if necessary
@time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
begin
- @time_zone.period_for_local(@time)
+ period || @time_zone.period_for_local(@time)
rescue ::TZInfo::PeriodNotFound
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
@time += 1.hour
@@ -390,7 +390,8 @@ module ActiveSupport
def wrap_with_time_zone(time)
if time.acts_like?(:time)
- self.class.new(nil, time_zone, time)
+ periods = time_zone.periods_for_local(time)
+ self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil)
elsif time.is_a?(Range)
wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
else
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index beaac42fa1..38f0d268f4 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -282,7 +282,7 @@ module ActiveSupport
#
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
- def parse(str, now=now)
+ def parse(str, now=now())
parts = Date._parse(str, false)
return if parts.empty?
@@ -352,6 +352,10 @@ module ActiveSupport
tzinfo.period_for_local(time, dst)
end
+ def periods_for_local(time) #:nodoc:
+ tzinfo.periods_for_local(time)
+ end
+
def self.find_tzinfo(name)
TZInfo::TimezoneProxy.new(MAPPING[name] || name)
end
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index b3f0e7198d..fe03984546 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveSupport
- # Returns the version of the currently loaded ActiveSupport as a Gem::Version
+ # Returns the version of the currently loaded ActiveSupport as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveSupport.version.segments
- STRING = ActiveSupport.version.to_s
+ gem_version
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index d082a0a499..009ee4db90 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,7 +1,9 @@
require 'time'
require 'base64'
+require 'bigdecimal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/date_time/calculations'
module ActiveSupport
# = XmlMini
@@ -56,13 +58,13 @@ module ActiveSupport
# TODO use regexp instead of Date.parse
unless defined?(PARSING)
PARSING = {
- "symbol" => Proc.new { |symbol| symbol.to_sym },
+ "symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
"date" => Proc.new { |date| ::Date.parse(date) },
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
"integer" => Proc.new { |integer| integer.to_i },
"float" => Proc.new { |float| float.to_f },
"decimal" => Proc.new { |number| BigDecimal(number) },
- "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
+ "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
"string" => Proc.new { |string| string.to_s },
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
"base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index c3c65cf805..18923f61d1 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -297,20 +297,21 @@ module CacheStoreBehavior
@cache.write('foo', 'bar')
@cache.write('fud', 'biz')
- values = @cache.fetch_multi('foo', 'fu', 'fud') {|value| value * 2 }
+ values = @cache.fetch_multi('foo', 'fu', 'fud') { |value| value * 2 }
- assert_equal(["bar", "fufu", "biz"], values)
- assert_equal("fufu", @cache.read('fu'))
+ assert_equal({ 'foo' => 'bar', 'fu' => 'fufu', 'fud' => 'biz' }, values)
+ assert_equal('fufu', @cache.read('fu'))
end
def test_multi_with_objects
- foo = stub(:title => "FOO!", :cache_key => "foo")
- bar = stub(:cache_key => "bar")
+ foo = stub(:title => 'FOO!', :cache_key => 'foo')
+ bar = stub(:cache_key => 'bar')
- @cache.write('bar', "BAM!")
+ @cache.write('bar', 'BAM!')
- values = @cache.fetch_multi(foo, bar) {|object| object.title }
- assert_equal(["FOO!", "BAM!"], values)
+ values = @cache.fetch_multi(foo, bar) { |object| object.title }
+
+ assert_equal({ foo => 'FOO!', bar => 'BAM!' }, values)
end
def test_read_and_write_compressed_small_data
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index a74ee880b2..60bd8a06aa 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -5,7 +5,7 @@ class ConcernTest < ActiveSupport::TestCase
module Baz
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def baz
"baz"
end
@@ -33,6 +33,12 @@ class ConcernTest < ActiveSupport::TestCase
include Baz
+ module ClassMethods
+ def baz
+ "bar's baz + " + super
+ end
+ end
+
def bar
"bar"
end
@@ -73,7 +79,7 @@ class ConcernTest < ActiveSupport::TestCase
@klass.send(:include, Bar)
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
- assert_equal "baz", @klass.baz
+ assert_equal "bar's baz + baz", @klass.baz
assert @klass.included_modules.include?(ConcernTest::Bar)
end
diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
new file mode 100644
index 0000000000..e634679d20
--- /dev/null
+++ b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
@@ -0,0 +1,11 @@
+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/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index b386e55d6c..423a3f2e9d 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -2,18 +2,6 @@ require 'abstract_unit'
require 'active_support/core_ext/big_decimal'
class BigDecimalTest < ActiveSupport::TestCase
- def test_to_yaml
- 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
-
- def test_to_d
- bd = BigDecimal.new '10'
- assert_equal bd, bd.to_d
- end
-
def test_to_s
bd = BigDecimal.new '0.01'
assert_equal '0.01', bd.to_s
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
index 0e0742d147..447b1d10ad 100644
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -6,14 +6,18 @@ module DelegatingFixtures
end
class Child < Parent
- superclass_delegating_accessor :some_attribute
+ ActiveSupport::Deprecation.silence do
+ superclass_delegating_accessor :some_attribute
+ end
end
class Mokopuna < Child
end
class PercysMom
- superclass_delegating_accessor :superpower
+ ActiveSupport::Deprecation.silence do
+ superclass_delegating_accessor :superpower
+ end
end
class Percy < PercysMom
@@ -29,7 +33,10 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
def test_simple_accessor_declaration
- single_class.superclass_delegating_accessor :both
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :both
+ end
+
# Class should have accessor and mutator
# the instance should have an accessor only
assert_respond_to single_class, :both
@@ -40,7 +47,11 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
def test_simple_accessor_declaration_with_instance_reader_false
_instance_methods = single_class.public_instance_methods
- single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
+
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
+ end
+
assert_respond_to single_class, :no_instance_reader
assert_respond_to single_class, :no_instance_reader=
assert !_instance_methods.include?(:no_instance_reader)
@@ -49,7 +60,9 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
def test_working_with_simple_attributes
- single_class.superclass_delegating_accessor :both
+ assert_deprecated do
+ single_class.superclass_delegating_accessor :both
+ end
single_class.both = "HMMM"
@@ -65,7 +78,11 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
def test_child_class_delegates_to_parent_but_can_be_overridden
parent = Class.new
- parent.superclass_delegating_accessor :both
+
+ assert_deprecated do
+ parent.superclass_delegating_accessor :both
+ end
+
child = Class.new(parent)
parent.both = "1"
assert_equal "1", child.both
@@ -97,4 +114,9 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
Child.some_attribute=nil
end
+ def test_deprecation_warning
+ assert_deprecated(/superclass_delegating_accessor is deprecated/) do
+ single_class.superclass_delegating_accessor :test_attribute
+ end
+ end
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 28ba33331e..c8f17f4618 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -31,6 +31,13 @@ class DurationTest < ActiveSupport::TestCase
assert !(1.day == 'foo')
end
+ def test_eql
+ assert 1.minute.eql?(1.minute)
+ assert 2.days.eql?(48.hours)
+ assert !1.second.eql?(1)
+ assert !1.eql?(1.second)
+ end
+
def test_inspect
assert_equal '0 seconds', 0.seconds.inspect
assert_equal '1 month', 1.month.inspect
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 6781e3c20e..6fcf6e8743 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -8,7 +8,6 @@ class SummablePayment < Payment
end
class EnumerableTests < ActiveSupport::TestCase
- Enumerator = [].each.class
class GenericEnumerable
include Enumerable
@@ -21,26 +20,6 @@ class EnumerableTests < ActiveSupport::TestCase
end
end
- def test_group_by
- names = %w(marcel sam david jeremy)
- klass = Struct.new(:name)
- objects = (1..50).map do
- klass.new names.sample
- end
-
- enum = GenericEnumerable.new(objects)
- grouped = enum.group_by { |object| object.name }
-
- grouped.each do |name, group|
- assert group.all? { |person| person.name == name }
- end
-
- assert_equal objects.uniq.map(&:name), grouped.keys
- assert({}.merge(grouped), "Could not convert ActiveSupport::OrderedHash into Hash")
- assert_equal Enumerator, enum.group_by.class
- assert_equal grouped, enum.group_by.each(&:name)
- end
-
def test_sums
enum = GenericEnumerable.new([5, 15, 10])
assert_equal 30, enum.sum
@@ -94,6 +73,10 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by { |p| p.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 })
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 40c8f03374..d824a16e98 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -23,6 +23,16 @@ class HashExtTest < ActiveSupport::TestCase
end
end
+ class HashByConversion
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_hash
+ @hash
+ end
+ end
+
def setup
@strings = { 'a' => 1, 'b' => 2 }
@nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
@@ -411,6 +421,12 @@ class HashExtTest < ActiveSupport::TestCase
assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
end
+ def test_update_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash.update HashByConversion.new({ :a => 1 })
+ assert_equal hash['a'], 1
+ end
+
def test_indifferent_merging
hash = HashWithIndifferentAccess.new
hash[:a] = 'failure'
@@ -430,6 +446,12 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 2, hash['b']
end
+ def test_merge_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ merged = hash.merge HashByConversion.new({ :a => 1 })
+ assert_equal merged['a'], 1
+ end
+
def test_indifferent_replace
hash = HashWithIndifferentAccess.new
hash[:a] = 42
@@ -442,6 +464,18 @@ class HashExtTest < ActiveSupport::TestCase
assert_same hash, replaced
end
+ def test_replace_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(HashByConversion.new(b: 12))
+
+ assert hash.key?('b')
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
def test_indifferent_merging_with_block
hash = HashWithIndifferentAccess.new
hash[:a] = 1
@@ -601,7 +635,7 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, h['first']
end
- def test_indifferent_subhashes
+ def test_indifferent_sub_hashes
h = {'user' => {'id' => 5}}.with_indifferent_access
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
@@ -893,6 +927,12 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({}, h.compact!)
assert_equal({}, h)
end
+
+ def test_new_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
+ assert hash.key?('a')
+ assert_equal 1, hash[:a]
+ end
end
class IWriteMyOwnXML
diff --git a/activesupport/test/core_ext/kernel/concern_test.rb b/activesupport/test/core_ext/kernel/concern_test.rb
new file mode 100644
index 0000000000..9b1fdda3b0
--- /dev/null
+++ b/activesupport/test/core_ext/kernel/concern_test.rb
@@ -0,0 +1,12 @@
+require 'abstract_unit'
+require 'active_support/core_ext/kernel/concern'
+
+class KernelConcernTest < ActiveSupport::TestCase
+ def test_may_be_defined_at_toplevel
+ mod = ::TOPLEVEL_BINDING.eval 'concern(:ToplevelConcern) { }'
+ assert_equal mod, ::ToplevelConcern
+ assert_kind_of ActiveSupport::Concern, ::ToplevelConcern
+ assert !Object.ancestors.include?(::ToplevelConcern), mod.ancestors.inspect
+ Object.send :remove_const, :ToplevelConcern
+ end
+end
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index 18b251173f..d8bf81d02b 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -137,4 +137,4 @@ class KernelDebuggerTest < ActiveSupport::TestCase
ensure
Object.send(:remove_const, :Rails)
end
-end
+end if RUBY_VERSION < '2.0.0'
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
index c6863b24a4..07d860b71c 100644
--- a/activesupport/test/core_ext/module/concerning_test.rb
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -1,35 +1,65 @@
require 'abstract_unit'
require 'active_support/core_ext/module/concerning'
-class ConcerningTest < ActiveSupport::TestCase
- def test_concern_shortcut_creates_a_module_but_doesnt_include_it
- mod = Module.new { concern(:Foo) { } }
- assert_kind_of Module, mod::Foo
- assert mod::Foo.respond_to?(:included)
- assert !mod.ancestors.include?(mod::Foo), mod.ancestors.inspect
+class ModuleConcerningTest < ActiveSupport::TestCase
+ def test_concerning_declares_a_concern_and_includes_it_immediately
+ klass = Class.new { concerning(:Foo) { } }
+ assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
end
+end
+class ModuleConcernTest < ActiveSupport::TestCase
def test_concern_creates_a_module_extended_with_active_support_concern
klass = Class.new do
- concern :Foo do
+ concern :Baz do
included { @foo = 1 }
def should_be_public; end
end
end
# Declares a concern but doesn't include it
- assert_kind_of Module, klass::Foo
- assert !klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
+ assert klass.const_defined?(:Baz, false)
+ assert !ModuleConcernTest.const_defined?(:Baz)
+ assert_kind_of ActiveSupport::Concern, klass::Baz
+ assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect
# Public method visibility by default
- assert klass::Foo.public_instance_methods.map(&:to_s).include?('should_be_public')
+ assert klass::Baz.public_instance_methods.map(&:to_s).include?('should_be_public')
# Calls included hook
- assert_equal 1, Class.new { include klass::Foo }.instance_variable_get('@foo')
+ assert_equal 1, Class.new { include klass::Baz }.instance_variable_get('@foo')
end
- def test_concerning_declares_a_concern_and_includes_it_immediately
- klass = Class.new { concerning(:Foo) { } }
- assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
+ class Foo
+ concerning :Bar do
+ module ClassMethods
+ def will_be_orphaned; end
+ end
+
+ const_set :ClassMethods, Module.new {
+ def hacked_on; end
+ }
+
+ # Doesn't overwrite existing ClassMethods module.
+ class_methods do
+ def nicer_dsl; end
+ end
+
+ # Doesn't overwrite previous class_methods definitions.
+ class_methods do
+ def doesnt_clobber; end
+ end
+ end
+ end
+
+ def test_using_class_methods_blocks_instead_of_ClassMethods_module
+ assert !Foo.respond_to?(:will_be_orphaned)
+ assert Foo.respond_to?(:hacked_on)
+ assert Foo.respond_to?(:nicer_dsl)
+ assert Foo.respond_to?(:doesnt_clobber)
+
+ # Orphan in Foo::ClassMethods, not Bar::ClassMethods.
+ assert Foo.const_defined?(:ClassMethods)
+ assert Foo::ClassMethods.method_defined?(:will_be_orphaned)
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 5b99fae411..ff6e21854e 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -245,6 +245,16 @@ class ModuleTest < ActiveSupport::TestCase
end
end
+ def test_delegation_line_number
+ _, line = Someone.instance_method(:foo).source_location
+ assert_equal Someone::FAILED_DELEGATE_LINE, line
+ end
+
+ def test_delegate_line_with_nil
+ _, line = Someone.instance_method(:bar).source_location
+ assert_equal Someone::FAILED_DELEGATE_LINE_2, line
+ end
+
def test_delegation_exception_backtrace
someone = Someone.new("foo", "bar")
someone.foo
diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 91d558dbb5..91d558dbb5 100644
--- a/activesupport/test/core_ext/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index e0566e012c..84512380cf 100644
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -5,34 +5,27 @@ require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
- YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
- NO = []
+ 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')
- YES << bd.dup
+ ALLOW_DUP << bd.dup
rescue TypeError
RAISE_DUP << bd
end
-
def test_duplicable
- (RAISE_DUP + NO).each do |v|
+ RAISE_DUP.each do |v|
assert !v.duplicable?
+ assert_raises(TypeError, v.class.name) { v.dup }
end
- YES.each do |v|
- assert v.duplicable?, "#{v.class} should be duplicable"
- end
-
- (YES + NO).each do |v|
+ ALLOW_DUP.each do |v|
+ assert v.duplicable?, "#{ v.class } should be duplicable"
assert_nothing_raised { v.dup }
end
-
- RAISE_DUP.each do |v|
- assert_raises(TypeError, v.class.name) do
- v.dup
- end
- end
end
end
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 478706eeae..b054a8dd31 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -47,4 +47,9 @@ class InTest < ActiveSupport::TestCase
def test_no_method_catching
assert_raise(ArgumentError) { 1.in?(1) }
end
+
+ def test_presence_in
+ assert_equal "stuff", "stuff".presence_in(%w( lots of stuff ))
+ assert_nil "stuff".presence_in(%w( lots of crap ))
+ end
end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 92f996f9a4..f887a9e613 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -46,6 +46,19 @@ class ToQueryTest < ActiveSupport::TestCase
:person => {:id => [20, 10]}
end
+ def test_nested_empty_hash
+ assert_equal '',
+ {}.to_query
+ assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=',
+ { a: 1, b: { c: 3, d: {} } }
+ assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12',
+ { p: 12, b: { c: false, e: nil, f: '' } }
+ assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=',
+ { b: { c: 3, k: {}, f: '' } }
+ assert_query_equal 'a%5B%5D=&b=3',
+ {a: [], b: 3}
+ end
+
private
def assert_query_equal(expected, actual)
assert_equal expected.split('&'), actual.to_query.split('&')
diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb
new file mode 100644
index 0000000000..71980f6910
--- /dev/null
+++ b/activesupport/test/core_ext/securerandom_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+require 'active_support/core_ext/securerandom'
+
+class SecureRandomExt < ActiveSupport::TestCase
+ def test_v3_uuids
+ assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
+ assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_v5_uuids
+ assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
+ assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_uuid_v4_alias
+ assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid)
+ end
+
+ def test_invalid_hash_class
+ assert_raise ArgumentError do
+ SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3')
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index d4f8ba8cdd..95df173880 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -58,6 +58,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal("blargles", "blargle".pluralize(2))
end
+ test 'pluralize with count = 1 still returns new string' do
+ name = "Kuldeep"
+ assert_not_same name.pluralize(1), name
+ end
+
def test_singularize
SingularToPlural.each do |singular, plural|
assert_equal(singular, plural.singularize)
@@ -161,6 +166,10 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_html_escape
+ assert_equal 'Hello', ERB::Util.html_escape("hello").humanize
+ end
+
def test_ord
assert_equal 97, 'a'.ord
assert_equal 97, 'abc'.ord
@@ -292,6 +301,12 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.first(4)
end
+ test "#first with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.first(5)
+ assert_not_same different_string, string
+ end
+
test "#last returns the last character" do
assert_equal "o", "hello".last
assert_equal 'x', 'x'.last
@@ -304,6 +319,12 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.last(4)
end
+ test "#last with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.last(5)
+ assert_not_same different_string, string
+ end
+
test "access returns a real string" do
hash = {}
hash["h"] = true
@@ -604,6 +625,29 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert !@other_combination.html_safe?
end
+ test "Prepending safe onto unsafe yields unsafe" do
+ @string.prepend "other".html_safe
+ assert !@string.html_safe?
+ assert_equal @string, "otherhello"
+ end
+
+ test "Prepending unsafe onto safe yields escaped safe" do
+ other = "other".html_safe
+ other.prepend "<foo>"
+ assert other.html_safe?
+ 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/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 5494824a40..7fe4d4a6b2 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
require 'active_support/time'
-require 'active_support/json'
class TimeWithZoneTest < ActiveSupport::TestCase
@@ -66,25 +65,6 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst
end
- def test_to_json_with_use_standard_json_time_format_config_set_to_false
- old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, false
- assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(@twz)
- ensure
- ActiveSupport.use_standard_json_time_format = old
- end
-
- def test_to_json_with_use_standard_json_time_format_config_set_to_true
- old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true
- assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz)
- ensure
- ActiveSupport.use_standard_json_time_format = old
- end
-
- def test_to_json_when_wrapping_a_date_time
- twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone)
- assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(twz)
- end
-
def test_nsec
local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
@@ -131,6 +111,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12)
end
+ def test_xmlschema_with_nil_fractional_seconds
+ assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil)
+ end
+
def test_to_yaml
assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml)
end
@@ -511,6 +495,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect
end
+ def test_change_at_dst_boundary
+ twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid'])
+ assert_equal twz, twz.change(:min => 0)
+ end
+
+ def test_round_at_dst_boundary
+ twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid'])
+ assert_equal twz, twz.round
+ end
+
def test_advance
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 00bec5bd9d..4ca63b3417 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -948,6 +948,18 @@ class DependenciesTest < ActiveSupport::TestCase
Object.class_eval { remove_const :A if const_defined?(:A) }
end
+ def test_access_unloaded_constants_for_reload
+ with_autoloading_fixtures do
+ assert_kind_of Module, A
+ assert_kind_of Class, A::B # Necessary to load A::B for the test
+ ActiveSupport::Dependencies.mark_for_unload(A::B)
+ ActiveSupport::Dependencies.remove_unloadable_constants!
+
+ A::B # Make sure no circular dependency error
+ end
+ end
+
+
def test_autoload_once_paths_should_behave_when_recursively_loading
with_loading 'dependencies', 'autoloading_fixtures' do
ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last]
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 35967ba656..b0b4738eb3 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -200,6 +200,7 @@ class InflectorTest < ActiveSupport::TestCase
def test_demodulize
assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account")
assert_equal "Account", ActiveSupport::Inflector.demodulize("Account")
+ assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account")
assert_equal "", ActiveSupport::Inflector.demodulize("")
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 4bd1b2e47c..dd03a61176 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -314,7 +314,7 @@ module InflectorTestCases
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves',
- 'cow' => 'kine',
+ 'cow' => 'kine', # Test inflections with different starting letters
'zombie' => 'zombies',
'genus' => 'genera'
}
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 78cf4819f9..f22d7b8b02 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -3,6 +3,7 @@ require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
require 'active_support/json'
+require 'active_support/time'
class TestJSONEncoding < ActiveSupport::TestCase
class Foo
@@ -226,21 +227,17 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_time_to_json_includes_local_offset
- prev = ActiveSupport.use_standard_json_time_format
- ActiveSupport.use_standard_json_time_format = true
- with_env_tz 'US/Eastern' do
- assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
+ with_standard_json_time_format(true) do
+ with_env_tz 'US/Eastern' do
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
+ end
end
- ensure
- ActiveSupport.use_standard_json_time_format = prev
end
def test_hash_with_time_to_json
- prev = ActiveSupport.use_standard_json_time_format
- ActiveSupport.use_standard_json_time_format = false
- assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
- ensure
- ActiveSupport.use_standard_json_time_format = prev
+ with_standard_json_time_format(false) do
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json
+ end
end
def test_nested_hash_with_float
@@ -330,12 +327,39 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
- def test_enumerable_should_pass_encoding_options_to_children_in_as_json
- people = [
- { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
- { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ People = Class.new(BasicObject) do
+ include Enumerable
+ def initialize()
+ @people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ end
+ def each(*, &blk)
+ @people.each do |p|
+ yield p if blk
+ p
+ end.each
+ end
+ end
+
+ def test_enumerable_should_generate_json_with_as_json
+ json = People.new.as_json :only => [:address, :city]
+ expected = [
+ { 'address' => { 'city' => 'London' }},
+ { 'address' => { 'city' => 'Paris' }}
]
- json = people.each.as_json :only => [:address, :city]
+
+ assert_equal(expected, json)
+ end
+
+ def test_enumerable_should_generate_json_with_to_json
+ json = People.new.to_json :only => [:address, :city]
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
+ end
+
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
+ json = People.new.each.as_json :only => [:address, :city]
expected = [
{ 'address' => { 'city' => 'London' }},
{ 'address' => { 'city' => 'Paris' }}
@@ -345,11 +369,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_enumerable_should_pass_encoding_options_to_children_in_to_json
- people = [
- { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
- { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
- ]
- json = people.each.to_json :only => [:address, :city]
+ json = People.new.each.to_json :only => [:address, :city]
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
@@ -453,6 +473,57 @@ EXPECTED
assert_nil h.as_json_called
end
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
+ with_standard_json_time_format(false) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
+ end
+ end
+
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
+ with_standard_json_time_format(true) do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
+ end
+ end
+
+ def test_twz_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
+ end
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ end
+
+ def test_time_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
+ end
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ end
+
+ def test_datetime_to_json_with_custom_time_precision
+ with_standard_json_time_format(true) do
+ ActiveSupport::JSON::Encoding.time_precision = 0
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
+ end
+ ensure
+ ActiveSupport::JSON::Encoding.time_precision = 3
+ end
+
+ def test_twz_to_json_when_wrapping_a_date_time
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
+ end
+
protected
def object_keys(json_object)
@@ -465,4 +536,11 @@ EXPECTED
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ def with_standard_json_time_format(boolean = true)
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
+ yield
+ ensure
+ ActiveSupport.use_standard_json_time_format = old
+ end
end
diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance_test.rb
index 2baf724da4..6ab8fa28ee 100644
--- a/activesupport/test/multibyte_conformance.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -20,8 +20,8 @@ class Downloader
target.write l
end
end
- end
- end
+ end
+ end
end
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 6d8d835de7..9bdb92024e 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -79,6 +79,9 @@ module ActiveSupport
assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ','))
assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %"))
+ assert_equal("98a%", number_helper.number_to_percentage("98a"))
+ assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN))
+ assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY))
end
end
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 0b54026c64..460a61613e 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -120,7 +120,9 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_select
- assert_equal @keys, @ordered_hash.select { true }.map(&:first)
+ new_ordered_hash = @ordered_hash.select { true }
+ assert_equal @keys, new_ordered_hash.map(&:first)
+ assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash
end
def test_delete_if
@@ -143,6 +145,7 @@ class OrderedHashTest < ActiveSupport::TestCase
assert_equal copy, @ordered_hash
assert !new_ordered_hash.keys.include?('pink')
assert @ordered_hash.keys.include?('pink')
+ assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash
end
def test_clear
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 8a71ef4324..0fa08c0e3a 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -162,6 +162,10 @@ class TimeHelperTest < ActiveSupport::TestCase
Time.stubs now: Time.now
end
+ teardown do
+ travel_back
+ end
+
def test_time_helper_travel
expected_time = Time.now + 1.day
travel 1.day
@@ -201,4 +205,16 @@ class TimeHelperTest < ActiveSupport::TestCase
assert_not_equal expected_time, Time.now
assert_not_equal Date.new(2004, 11, 24), Date.today
end
+
+ def test_time_helper_travel_back
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+
+ travel_to expected_time
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ travel_back
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 1107b48460..79ec57af2b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -97,6 +97,7 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
+ travel_back
end
def test_tomorrow
@@ -108,6 +109,7 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_back
end
def test_yesterday
@@ -119,6 +121,33 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_back
+ end
+
+ def test_travel_to_a_date
+ with_env_tz do
+ Time.use_zone('Hawaii') do
+ date = Date.new(2014, 2, 18)
+ time = date.midnight
+
+ travel_to date do
+ assert_equal date, Date.current
+ assert_equal time, Time.current
+ end
+ end
+ end
+ end
+
+ def test_travel_to_travels_back_and_reraises_if_the_block_raises
+ ts = Time.current - 1.second
+
+ travel_to ts do
+ raise
+ end
+
+ flunk # ensure travel_to re-raises
+ rescue
+ assert_not_equal ts, Time.current
end
def test_local
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index d992028323..f49431cbbf 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,6 +1,9 @@
require 'abstract_unit'
require 'active_support/xml_mini'
require 'active_support/builder'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash'
+require 'active_support/core_ext/big_decimal'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase
@@ -88,6 +91,61 @@ module XmlMiniTest
assert_xml "<b>Howdy</b>"
end
+ test "#to_tag should use the type value in the options hash" do
+ @xml.to_tag(:b, "blue", @options.merge(type: 'color'))
+ assert_xml( "<b type=\"color\">blue</b>" )
+ end
+
+ test "#to_tag accepts symbol types" do
+ @xml.to_tag(:b, :name, @options)
+ assert_xml( "<b type=\"symbol\">name</b>" )
+ end
+
+ test "#to_tag accepts boolean types" do
+ @xml.to_tag(:b, true, @options)
+ assert_xml( "<b type=\"boolean\">true</b>")
+ end
+
+ test "#to_tag accepts float types" do
+ @xml.to_tag(:b, 3.14, @options)
+ assert_xml( "<b type=\"float\">3.14</b>")
+ end
+
+ test "#to_tag accepts decimal types" do
+ @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
+ assert_xml( "<b type=\"decimal\">1.2</b>")
+ end
+
+ test "#to_tag accepts date types" do
+ @xml.to_tag(:b, Date.new(2001,2,3), @options)
+ assert_xml( "<b type=\"date\">2001-02-03</b>")
+ end
+
+ test "#to_tag accepts datetime types" do
+ @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options)
+ assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>")
+ end
+
+ test "#to_tag accepts time types" do
+ @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options)
+ assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>")
+ end
+
+ test "#to_tag accepts array types" do
+ @xml.to_tag(:b, ["first_name", "last_name"], @options)
+ assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" )
+ end
+
+ test "#to_tag accepts hash types" do
+ @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options)
+ assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" )
+ end
+
+ test "#to_tag should not add type when skip types option is set" do
+ @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1))
+ assert_xml( "<b>Bob</b>" )
+ end
+
test "#to_tag should dasherize the space when passed a string with spaces as a key" do
@xml.to_tag("New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
@@ -97,7 +155,6 @@ module XmlMiniTest
@xml.to_tag(:"New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
end
- # TODO: test the remaining functions hidden in #to_tag.
end
class WithBackendTest < ActiveSupport::TestCase
@@ -169,4 +226,128 @@ module XmlMiniTest
end
end
end
+
+ class ParsingTest < ActiveSupport::TestCase
+ def setup
+ @parsing = ActiveSupport::XmlMini::PARSING
+ end
+
+ def test_symbol
+ parser = @parsing['symbol']
+ assert_equal :symbol, parser.call('symbol')
+ assert_equal :symbol, parser.call(:symbol)
+ assert_equal :'123', parser.call(123)
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_date
+ parser = @parsing['date']
+ assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z")
+ assert_raises(TypeError) { parser.call(1384190018) }
+ assert_raises(ArgumentError) { parser.call("not really a date") }
+ end
+
+ def test_datetime
+ parser = @parsing['datetime']
+ assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z")
+ assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z")
+ assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z")
+ assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9")
+ assert_raises(ArgumentError) { parser.call("1384190018") }
+ end
+
+ def test_integer
+ parser = @parsing['integer']
+ assert_equal 123, parser.call(123)
+ assert_equal 123, parser.call(123.003)
+ assert_equal 123, parser.call("123")
+ assert_equal 0, parser.call("")
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_float
+ parser = @parsing['float']
+ assert_equal 123, parser.call("123")
+ assert_equal 123.003, parser.call("123.003")
+ assert_equal 123.0, parser.call("123,003")
+ assert_equal 0.0, parser.call("")
+ assert_equal 123, parser.call(123)
+ assert_equal 123.05, parser.call(123.05)
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_decimal
+ parser = @parsing['decimal']
+ assert_equal 123, parser.call("123")
+ assert_equal 123.003, parser.call("123.003")
+ assert_equal 123.0, parser.call("123,003")
+ assert_equal 0.0, parser.call("")
+ assert_equal 123, parser.call(123)
+ assert_raises(ArgumentError) { parser.call(123.04) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_boolean
+ parser = @parsing['boolean']
+ [1, true, "1"].each do |value|
+ assert parser.call(value)
+ end
+
+ [0, false, "0"].each do |value|
+ assert_not parser.call(value)
+ end
+ end
+
+ def test_string
+ parser = @parsing['string']
+ assert_equal "123", parser.call(123)
+ assert_equal "123", parser.call("123")
+ assert_equal "[]", parser.call("[]")
+ assert_equal "[]", parser.call([])
+ assert_equal "{}", parser.call({})
+ assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ end
+
+ def test_yaml
+ yaml = <<YAML
+product:
+ - sku : BL394D
+ quantity : 4
+ description : Basketball
+YAML
+ expected = {
+ "product"=> [
+ {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"}
+ ]
+ }
+ parser = @parsing['yaml']
+ assert_equal(expected, parser.call(yaml))
+ assert_equal({1 => 'test'}, parser.call({1 => 'test'}))
+ assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}"))
+ end
+
+ def test_base64Binary_and_binary
+ base64 = <<BASE64
+TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
+IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
+dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
+dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
+ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
+BASE64
+ expected_base64 = <<EXPECTED
+Man is distinguished, not only by his reason, but by this singular passion from
+other animals, which is a lust of the mind, that by a perseverance of delight
+in the continued and indefatigable generation of knowledge, exceeds the short
+vehemence of any carnal pleasure.
+EXPECTED
+
+ parser = @parsing['base64Binary']
+ assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64)
+ parser.call("NON BASE64 INPUT")
+
+ parser = @parsing['binary']
+ assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, 'encoding' => 'base64')
+ assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {})
+ end
+ end
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 4cfc5b1f10..14ad4fd424 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,13 +1,9 @@
-* Fixed missing line and shadow on service pages(404, 422, 500).
+* Updates the maintenance policy to match the latest versions of Rails
- *Dmitry Korotkov*
-
-* Removed repetitive th tags. Instead of them added one th tag with a colspan attribute.
-
- *Sıtkı Bağdat*
+ *Matias Korhonen*
-* Added the Rails maintenance policy to the guides.
+* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first.
- *Matias Korhonen*
+ *Alex Riabov*
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/Rakefile b/guides/Rakefile
index d6dd950d01..94d4be8c0a 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -13,8 +13,8 @@ namespace :guides do
desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing"
task :kindle do
- unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/
- abort "Please `gem install kindlerb`"
+ unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/
+ abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH"
end
unless `convert` =~ /convert/
abort "Please install ImageMagick`"
diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png
new file mode 100644
index 0000000000..117a78a39f
--- /dev/null
+++ b/guides/assets/images/getting_started/article_with_comments.png
Binary files differ
diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png
index 4a30e49e6d..5b88a842b2 100644
--- a/guides/assets/images/getting_started/challenge.png
+++ b/guides/assets/images/getting_started/challenge.png
Binary files differ
diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png
index 1a13eddd91..9755f581a6 100644
--- a/guides/assets/images/getting_started/confirm_dialog.png
+++ b/guides/assets/images/getting_started/confirm_dialog.png
Binary files differ
diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
new file mode 100644
index 0000000000..9f32c68472
--- /dev/null
+++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
Binary files differ
diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png
deleted file mode 100644
index 6c78e52173..0000000000
--- a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png
index 6910e1647e..98bff37d4a 100644
--- a/guides/assets/images/getting_started/form_with_errors.png
+++ b/guides/assets/images/getting_started/form_with_errors.png
Binary files differ
diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png
index bf23cba231..0566a3ffde 100644
--- a/guides/assets/images/getting_started/index_action_with_edit_link.png
+++ b/guides/assets/images/getting_started/index_action_with_edit_link.png
Binary files differ
diff --git a/guides/assets/images/getting_started/new_article.png b/guides/assets/images/getting_started/new_article.png
new file mode 100644
index 0000000000..bd3ae4fa67
--- /dev/null
+++ b/guides/assets/images/getting_started/new_article.png
Binary files differ
diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png
deleted file mode 100644
index b20b0192d4..0000000000
--- a/guides/assets/images/getting_started/new_post.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png
deleted file mode 100644
index e13095ff8f..0000000000
--- a/guides/assets/images/getting_started/post_with_comments.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/rails_welcome.jpg b/guides/assets/images/getting_started/rails_welcome.jpg
deleted file mode 100644
index 65a44cdfe5..0000000000
--- a/guides/assets/images/getting_started/rails_welcome.jpg
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png
new file mode 100644
index 0000000000..3e07c948a0
--- /dev/null
+++ b/guides/assets/images/getting_started/rails_welcome.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png
index 35ee4f348f..ed62862291 100644
--- a/guides/assets/images/getting_started/routing_error_no_controller.png
+++ b/guides/assets/images/getting_started/routing_error_no_controller.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png
index 1cbddfa0f1..08c54f921f 100644
--- a/guides/assets/images/getting_started/routing_error_no_route_matches.png
+++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_articles.png b/guides/assets/images/getting_started/show_action_for_articles.png
new file mode 100644
index 0000000000..4dad704f89
--- /dev/null
+++ b/guides/assets/images/getting_started/show_action_for_articles.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png
deleted file mode 100644
index 9467df6a07..0000000000
--- a/guides/assets/images/getting_started/show_action_for_posts.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/template_is_missing_articles_new.png b/guides/assets/images/getting_started/template_is_missing_articles_new.png
new file mode 100644
index 0000000000..4e636d09ff
--- /dev/null
+++ b/guides/assets/images/getting_started/template_is_missing_articles_new.png
Binary files differ
diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png
deleted file mode 100644
index f03db05fb8..0000000000
--- a/guides/assets/images/getting_started/template_is_missing_posts_new.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png
deleted file mode 100644
index c29cb2f54f..0000000000
--- a/guides/assets/images/getting_started/undefined_method_post_path.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_articles.png b/guides/assets/images/getting_started/unknown_action_create_for_articles.png
new file mode 100644
index 0000000000..fd20cd53dc
--- /dev/null
+++ b/guides/assets/images/getting_started/unknown_action_create_for_articles.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
deleted file mode 100644
index 8fdd4c574a..0000000000
--- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_new_for_articles.png b/guides/assets/images/getting_started/unknown_action_new_for_articles.png
new file mode 100644
index 0000000000..e948a51e4a
--- /dev/null
+++ b/guides/assets/images/getting_started/unknown_action_new_for_articles.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png
deleted file mode 100644
index 7e72feee38..0000000000
--- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png
+++ /dev/null
Binary files differ
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index a2155c43b9..c3d7e96c4d 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -23,11 +23,11 @@ gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
-# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring
+# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring', group: :development
# Use ActiveModel has_secure_password
-# gem 'bcrypt-ruby', '~> 3.1.2'
+# gem 'bcrypt', '~> 3.1.7'
# Use unicorn as the app server
# gem 'unicorn'
diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile
index 05de8bb536..ba6b733dd2 100644
--- a/guides/code/getting_started/Rakefile
+++ b/guides/code/getting_started/Rakefile
@@ -3,4 +3,4 @@
require File.expand_path('../config/application', __FILE__)
-Blog::Application.load_tasks
+Rails.application.load_tasks
diff --git a/guides/code/getting_started/app/views/layouts/application.html.erb b/guides/code/getting_started/app/views/layouts/application.html.erb
index 95368c37a3..d0ba8415e6 100644
--- a/guides/code/getting_started/app/views/layouts/application.html.erb
+++ b/guides/code/getting_started/app/views/layouts/application.html.erb
@@ -2,8 +2,8 @@
<html>
<head>
<title>Blog</title>
- <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
- <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru
index ddf869e921..5bc2a619e8 100644
--- a/guides/code/getting_started/config.ru
+++ b/guides/code/getting_started/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run Blog::Application
+run Rails.application
diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb
index e7e341c960..ee8d90dc65 100644
--- a/guides/code/getting_started/config/environment.rb
+++ b/guides/code/getting_started/config/environment.rb
@@ -2,4 +2,4 @@
require File.expand_path('../application', __FILE__)
# Initialize the Rails application.
-Blog::Application.initialize!
+Rails.application.initialize!
diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb
index 7e5692b08b..ae9ffe209a 100644
--- a/guides/code/getting_started/config/environments/development.rb
+++ b/guides/code/getting_started/config/environments/development.rb
@@ -1,4 +1,4 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index 93d44723fb..c8ae858574 100644
--- a/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -1,11 +1,11 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
- # your application in memory, allowing both thread web servers
+ # your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb
index 00adaa5015..680d0b9e06 100644
--- a/guides/code/getting_started/config/environments/test.rb
+++ b/guides/code/getting_started/config/environments/test.rb
@@ -1,4 +1,4 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
@@ -14,7 +14,7 @@ Blog::Application.configure do
# Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
- config.static_cache_control = "public, max-age=3600"
+ config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
config.consider_all_requests_local = true
diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb
index aaf57731be..c2a549c299 100644
--- a/guides/code/getting_started/config/initializers/secret_token.rb
+++ b/guides/code/getting_started/config/initializers/secret_token.rb
@@ -9,4 +9,4 @@
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53'
+Rails.application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53'
diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb
index 3b2ca93ab9..1b9fa324d4 100644
--- a/guides/code/getting_started/config/initializers/session_store.rb
+++ b/guides/code/getting_started/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Blog::Application.config.session_store :cookie_store, key: '_blog_session'
+Rails.application.config.session_store :cookie_store, key: '_blog_session'
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index 0155b613a3..65d273b58d 100644
--- a/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,4 +1,4 @@
-Blog::Application.routes.draw do
+Rails.application.routes.draw do
resources :posts do
resources :comments
end
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index 760b196abd..169453400f 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -1,3 +1,5 @@
+require 'yaml'
+
module RailsGuides
module Helpers
def guide(name, url, options = {}, &block)
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index cf9d694de7..2d4be0cda7 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -294,7 +294,7 @@ NOTE. The old style `map` commands still work as before with a backwards compati
Deprecations
* The catch all route for non-REST applications (`/:controller/:action/:id`) is now commented out.
-* Routes :path\_prefix no longer exists and :name\_prefix now automatically adds "\_" at the end of the given value.
+* Routes `:path_prefix` no longer exists and `:name_prefix` now automatically adds "_" at the end of the given value.
More Information:
* [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/)
@@ -574,7 +574,7 @@ The following methods have been removed because they are no longer used in the f
Action Mailer
-------------
-Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the Email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably.
+Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably.
* All mailers are now in `app/mailers` by default.
* Can now send email using new API with three methods: `attachments`, `headers` and `mail`.
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index ce811a583b..cdcde67869 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -187,7 +187,7 @@ Action Pack
Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions.
-* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`.
+* `form_for` is changed to use `#{action}_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}_#{action}`.
* `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`.
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 924e5d90db..822943d81e 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -64,7 +64,7 @@ Spring is running:
```
Have a look at the
-[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to
+[Spring README](https://github.com/rails/spring/blob/master/README.md) to
see all available features.
See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#spring)
@@ -175,6 +175,8 @@ conversation.active? # => false
conversation.status # => "archived"
Conversation.archived # => Relation for all archived Conversations
+
+Conversation.statuses # => { "active" => 0, "archived" => 1 }
```
See its
@@ -241,6 +243,7 @@ unless they use `xhr`. Upgrade your tests to be explicit about expecting
XmlHttpRequests. Instead of `post :create, format: :js`, switch to the explicit
`xhr :post, :create, format: :js`.
+
Railties
--------
@@ -267,7 +270,7 @@ for detailed changes.
### Notable changes
* The [Spring application
- preloader](https://github.com/jonleighton/spring) is now installed
+ preloader](https://github.com/rails/spring) is now installed
by default for new applications. It uses the development group of
the Gemfile, so will not be installed in
production. ([Pull Request](https://github.com/rails/rails/pull/12958))
@@ -278,7 +281,7 @@ for detailed changes.
* Exposed `MiddlewareStack#unshift` to environment
configuration. ([Pull Request](https://github.com/rails/rails/pull/12479))
-* Add `Application#message_verifier` method to return a message
+* Added `Application#message_verifier` method to return a message
verifier. ([Pull Request](https://github.com/rails/rails/pull/12995))
* The `test_help.rb` file which is required by the default generated test
@@ -288,6 +291,11 @@ for detailed changes.
with `config.active_record.maintain_test_schema = false`. ([Pull
Request](https://github.com/rails/rails/pull/13528))
+* Introduce `Rails.gem_version` as a convenience method to return
+ `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform
+ version comparison. ([Pull Request](https://github.com/rails/rails/pull/14103))
+
+
Action Pack
-----------
@@ -335,6 +343,24 @@ for detailed changes.
* Separated Action View completely from Action
Pack. ([Pull Request](https://github.com/rails/rails/pull/11032))
+* Log which keys were affected by deep
+ munge. ([Pull Request](https://github.com/rails/rails/pull/13813))
+
+* New config option `config.action_dispatch.perform_deep_munge` to opt out of
+ params "deep munging" that was used to address security vulnerability
+ CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188))
+
+* New config option `config.action_dispatch.cookies_serializer` for specifying a
+ serializer for the signed and encrypted cookie jars. (Pull Requests
+ [1](https://github.com/rails/rails/pull/13692),
+ [2](https://github.com/rails/rails/pull/13945) /
+ [More Details](upgrading_ruby_on_rails.html#cookies-serializer))
+
+* Added `render :plain`, `render :html` and `render
+ :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) /
+ [More Details](upgrading_ruby_on_rails.html#rendering-content-from-string))
+
+
Action Mailer
-------------
@@ -344,9 +370,13 @@ for detailed changes.
### Notable changes
+* Added mailer previews feature based on 37 Signals mail_view
+ gem. ([Commit](https://github.com/rails/rails/commit/d6dec7fcb6b8fddf8c170182d4fe64ecfc7b2261))
+
* Instrument the generation of Action Mailer messages. The time it takes to
generate a message is written to the log. ([Pull Request](https://github.com/rails/rails/pull/12556))
+
Active Record
-------------
@@ -366,7 +396,7 @@ for detailed changes.
* Removed deprecated `scope` use without passing a callable object.
* Removed deprecated `transaction_joinable=` in favor of `begin_transaction`
- with `d:joinable` option.
+ with a `:joinable` option.
* Removed deprecated `decrement_open_transactions`.
@@ -411,6 +441,8 @@ for detailed changes.
* Remove implicit join references that were deprecated in 4.0.
* Removed `activerecord-deprecated_finders` as a dependency.
+ Please see [the gem README](https://github.com/rails/activerecord-deprecated_finders#active-record-deprecated-finders)
+ for more info.
* Removed usage of `implicit_readonly`. Please use `readonly` method
explicitly to mark records as
@@ -420,11 +452,6 @@ for detailed changes.
* Deprecated `quoted_locking_column` method, which isn't used anywhere.
-* Deprecated the delegation of Array bang methods for associations.
- To use them, instead first call `#to_a` on the association to access the
- array to be acted
- on. ([Pull Request](https://github.com/rails/rails/pull/12129))
-
* Deprecated `ConnectionAdapters::SchemaStatements#distinct`,
as it is no longer used by internals. ([Pull Request](https://github.com/rails/rails/pull/10556))
@@ -438,6 +465,12 @@ for detailed changes.
### Notable changes
+* Default scopes are no longer overridden by chained conditions.
+
+ Before this change when you defined a `default_scope` in a model
+ it was overridden by chained conditions in the same field. Now it
+ is merged like any other scope. [More Details](upgrading_ruby_on_rails.html#changes-on-default-scopes).
+
* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from
a model's attribute or
method. ([Pull Request](https://github.com/rails/rails/pull/12891))
@@ -498,7 +531,35 @@ for detailed changes.
object. Helper methods used by multiple fixtures should be defined on modules
included in `ActiveRecord::FixtureSet.context_class`. ([Pull Request](https://github.com/rails/rails/pull/13022))
-* Don't create or drop the test database if RAILS_ENV is specified explicitly.
+* Don't create or drop the test database if RAILS_ENV is specified
+ explicitly. ([Pull Request](https://github.com/rails/rails/pull/13629))
+
+* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert
+ to an `Array` by calling `#to_a` before using these methods. ([Pull Request](https://github.com/rails/rails/pull/13314))
+
+* `find_in_batches`, `find_each`, `Result#each` and `Enumerable#index_by` now
+ return an `Enumerator` that can calculate its
+ size. ([Pull Request](https://github.com/rails/rails/pull/13938))
+
+* `scope`, `enum` and Associations now raise on "dangerous" name
+ conflicts. ([Pull Request](https://github.com/rails/rails/pull/13450),
+ [Pull Request](https://github.com/rails/rails/pull/13896))
+
+* `second` through `fifth` methods act like the `first`
+ finder. ([Pull Request](https://github.com/rails/rails/pull/13757))
+
+* Make `touch` fire the `after_commit` and `after_rollback`
+ callbacks. ([Pull Request](https://github.com/rails/rails/pull/12031))
+
+* Enable partial indexes for `sqlite >= 3.8.0`.
+ ([Pull Request](https://github.com/rails/rails/pull/13350))
+
+* Make `change_column_null`
+ revertible. ([Commit](https://github.com/rails/rails/commit/724509a9d5322ff502aefa90dd282ba33a281a96))
+
+* Added a flag to disable schema dump after migration. This is set to `false`
+ by default in the production environment for new applications.
+ ([Pull Request](https://github.com/rails/rails/pull/13948))
Active Model
------------
@@ -517,6 +578,13 @@ for detailed changes.
* Added new API methods `reset_changes` and `changes_applied` to
`ActiveModel::Dirty` that control changes state.
+* Ability to specify multiple contexts when defining a
+ validation. ([Pull Request](https://github.com/rails/rails/pull/13754))
+
+* `attribute_changed?` now accepts a hash to check if the attribute was changed
+ `:from` and/or `:to` a given
+ value. ([Pull Request](https://github.com/rails/rails/pull/13131))
+
Active Support
--------------
@@ -567,6 +635,12 @@ for detailed changes.
* Removed deprecated `assert_present` and `assert_blank` methods, use `assert
object.blank?` and `assert object.present?` instead.
+* Remove deprecated `#filter` method for filter objects, use the corresponding
+ method instead (e.g. `#before` for a before filter).
+
+* Removed 'cow' => 'kine' irregular inflection from default
+ inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9))
+
### Deprecations
* Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to
@@ -583,11 +657,14 @@ for detailed changes.
[More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
* Deprecated `ActiveSupport.encode_big_decimal_as_string` option. This feature has
- been extracetd into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
gem.
([Pull Request](https://github.com/rails/rails/pull/13060) /
[More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+* Deprecate custom `BigDecimal`
+ serialization. ([Pull Request](https://github.com/rails/rails/pull/13911))
+
### Notable changes
* `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the
@@ -600,9 +677,12 @@ for detailed changes.
[More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
* Added `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These
- methods change current time to the given time or time difference by stubbing
- `Time.now` and
- `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824))
+ methods change current time to the given time or duration by stubbing
+ `Time.now` and `Date.today`.
+
+* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method returns
+ the current time to the original state, by removing the stubs added by `travel`
+ and `travel_to`. ([Pull Request](https://github.com/rails/rails/pull/13884))
* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed
them to JavaScript functions like
@@ -613,11 +693,33 @@ for detailed changes.
`at_middle_of_day` as
aliases. ([Pull Request](https://github.com/rails/rails/pull/10879))
+* Added `Date#all_week/month/quarter/year` for generating date
+ ranges. ([Pull Request](https://github.com/rails/rails/pull/9685))
+
+* Added `Time.zone.yesterday` and
+ `Time.zone.tomorrow`. ([Pull Request](https://github.com/rails/rails/pull/12822))
+
* Added `String#remove(pattern)` as a short-hand for the common pattern of
`String#gsub(pattern,'')`. ([Commit](https://github.com/rails/rails/commit/5da23a3f921f0a4a3139495d2779ab0d3bd4cb5f))
-* Removed 'cow' => 'kine' irregular inflection from default
- inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9))
+* Added `Hash#compact` and `Hash#compact!` for removing items with nil value
+ from hash. ([Pull Request](https://github.com/rails/rails/pull/13632))
+
+* `blank?` and `present?` commit to return
+ singletons. ([Commit](https://github.com/rails/rails/commit/126dc47665c65cd129967cbd8a5926dddd0aa514))
+
+* Default the new `I18n.enforce_available_locales` config to `true`, meaning
+ `I18n` will make sure that all locales passed to it must be declared in the
+ `available_locales`
+ list. ([Pull Request](https://github.com/rails/rails/pull/13341))
+
+* Introduce `Module#concerning`: a natural, low-ceremony way to separate
+ responsibilities within a
+ class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81))
+
+* Added `Object#presence_in` to simplify value whitelisting.
+ ([Commit](https://github.com/rails/rails/commit/4edca106daacc5a159289eae255207d160f22396))
+
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 27c53689c4..7e39f761f2 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,12 +10,15 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 4.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.16/">http://guides.rubyonrails.org/v3.2.16/</a>.
+ The guides for Rails 4.0.x are available at <a href="http://guides.rubyonrails.org/v4.0.4/">http://guides.rubyonrails.org/v4.0.4/</a>.
+</p>
+<p>
+ The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>.
</p>
<p>
The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index f394daa6aa..1735188f27 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -34,7 +34,7 @@ The naming convention of controllers in Rails favors pluralization of the last w
Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details.
-NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form.
+NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form.
Methods and Actions
@@ -112,6 +112,10 @@ 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)
+for more information.
+
To send a hash you include the key name inside the brackets:
```html
@@ -256,7 +260,7 @@ used:
params.require(:log_entry).permit!
```
-This will mark the `:log_entry` parameters hash and any subhash of it
+This will mark the `:log_entry` parameters hash and any sub-hash of it
permitted. Extreme care should be taken when using `permit!` as it
will allow all current and future model attributes to be
mass-assigned.
@@ -360,33 +364,48 @@ If you need a different session storage mechanism, you can change it in the `con
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails g active_record:session_migration")
-# YourApp::Application.config.session_store :active_record_store
+# Rails.application.config.session_store :active_record_store
```
Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`:
```ruby
# Be sure to restart your server when you modify this file.
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session'
+Rails.application.config.session_store :cookie_store, key: '_your_app_session'
```
You can also pass a `:domain` key and specify the domain name for the cookie:
```ruby
# Be sure to restart your server when you modify this file.
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
+Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
```
-Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/initializers/secret_token.rb`
+Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml`
```ruby
# Be sure to restart your server when you modify this file.
-# Your secret key for verifying the integrity of signed cookies.
+# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
+
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
+# You can use `rake secret` to generate a secure secret key.
+
+# Make sure the secrets in this file are kept private
+# if you're sharing your code publicly.
+
+development:
+ secret_key_base: a75d...
+
+test:
+ secret_key_base: 492f...
+
+# Do not keep production secrets in the repository,
+# instead read values from the environment.
+production:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```
NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions.
@@ -568,6 +587,62 @@ end
Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`.
+Rails also provides a signed cookie jar and an encrypted cookie jar for storing
+sensitive data. The signed cookie jar appends a cryptographic signature on the
+cookie values to protect their integrity. The encrypted cookie jar encrypts the
+values in addition to signing them, so that they cannot be read by the end user.
+Refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html)
+for more details.
+
+These special cookie jars use a serializer to serialize the assigned values into
+strings and deserializes them into Ruby objects on read.
+
+You can specify what serializer to use:
+
+```ruby
+Rails.application.config.action_dispatch.cookies_serializer = :json
+```
+
+The default serializer for new applications is `:json`. For compatibility with
+old applications with existing cookies, `:marshal` is used when `serializer`
+option is not specified.
+
+You may also set this option to `:hybrid`, in which case Rails would transparently
+deserialize existing (`Marshal`-serialized) cookies on read and re-write them in
+the `JSON` format. This is useful for migrating existing applications to the
+`:json` serializer.
+
+It is also possible to pass a custom serializer that responds to `load` and
+`dump`:
+
+```ruby
+Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer
+```
+
+When using the `:json` or `:hybrid` serializer, you should beware that not all
+Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects
+will be serialized as strings, and `Hash`es will have their keys stringified.
+
+```ruby
+class CookiesController < ApplicationController
+ def set_cookie
+ cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
+ redirect_to action: 'read_cookie'
+ end
+
+ def read_cookie
+ cookies.encrypted[:expiration_date] # => "2014-03-20"
+ end
+end
+```
+
+It's advisable that you only store simple data (strings and numbers) in cookies.
+If you have to store complex objects, you would need to handle the conversion
+manually when reading the values on subsequent requests.
+
+If you use the cookie session store, this would apply to the `session` and
+`flash` hash as well.
+
Rendering XML and JSON data
---------------------------
@@ -683,7 +758,7 @@ class ApplicationController < ActionController::Base
end
class LoginFilter
- def self.filter(controller)
+ def self.before(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "You must be logged in to access this section"
controller.redirect_to controller.new_login_url
@@ -692,7 +767,7 @@ class LoginFilter
end
```
-Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method `filter` which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same `filter` method, which will get run in the same way. The method must `yield` to execute the action. Alternatively, it can have both a `before` and an `after` method that are run before and after the action.
+Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class must implement a method with the same name as the filter, so for the `before_action` filter the class must implement a `before` method, and so on. The `around` method must `yield` to execute the action.
Request Forgery Protection
--------------------------
@@ -1052,7 +1127,7 @@ class ApplicationController < ActionController::Base
private
def record_not_found
- render text: "404 Not Found", status: 404
+ render plain: "404 Not Found", status: 404
end
end
```
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 61fd762304..6dc7fb1606 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -138,7 +138,7 @@ When you call the `mail` method now, Action Mailer will detect the two templates
Mailers are really just another way to render a view. Instead of rendering a
view and sending out the HTTP protocol, they are just sending it out through the
-Email protocols instead. Due to this, it makes sense to just have your
+email protocols instead. Due to this, it makes sense to just have your
controller tell the Mailer to send an email when a user is successfully created.
Setting this up is painfully simple.
@@ -164,7 +164,7 @@ class UsersController < ApplicationController
respond_to do |format|
if @user.save
- # Tell the UserMailer to send a welcome Email after save
+ # Tell the UserMailer to send a welcome email after save
UserMailer.welcome_email(@user).deliver
format.html { redirect_to(@user, notice: 'User was successfully created.') }
@@ -611,7 +611,7 @@ files (environment.rb, production.rb, etc...)
|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>|
|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>|
|`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.|
-|`delivery_method`|Defines a delivery method. Possible values are `:smtp` (default), `:sendmail`, `:file` and `:test`.|
+|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.|
|`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
|`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
|`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).|
@@ -639,8 +639,8 @@ config.action_mailer.default_options = {from: 'no-reply@example.com'}
### Action Mailer Configuration for Gmail
-As Action Mailer now uses the Mail gem, this becomes as simple as adding to your
-`config/environments/$RAILS_ENV.rb` file:
+As Action Mailer now uses the [Mail gem](https://github.com/mikel/mail), this
+becomes as simple as adding to your `config/environments/$RAILS_ENV.rb` file:
```ruby
config.action_mailer.delivery_method = :smtp
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 6a355a5177..74f95bfcfd 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1550,7 +1550,7 @@ end
Sanitizes a block of CSS code.
-#### strip_links(html)
+#### strip_links(html)
Strips all link tags from text leaving just the link text.
```ruby
@@ -1568,9 +1568,9 @@ strip_links('Blog: <a href="http://myblog.com/">Visit</a>.')
# => Blog: Visit.
```
-#### strip_tags(html)
+#### strip_tags(html)
-Strips all HTML tags from the html, including comments.
+Strips all HTML tags from the html, including comments.
This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner.
```ruby
@@ -1585,6 +1585,17 @@ strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>")
NB: The output may still contain unescaped '<', '>', '&' characters and confuse browsers.
+### CsrfHelper
+
+Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
+request forgery protection parameter and token, respectively.
+
+```html
+<%= csrf_meta_tags %>
+```
+
+NOTE: Regular forms generate hidden fields so they do not use these tags. More
+details can be found in the [Rails Security Guide](security.html#cross-site-request-forgery-csrf).
Localized Views
---------------
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 667433285f..fbcce325ed 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -92,6 +92,7 @@ Here is a list with all the available Active Record callbacks, listed in the sam
* `around_create`
* `after_create`
* `after_save`
+* `after_commit/after_rollback`
### Updating an Object
@@ -103,12 +104,14 @@ Here is a list with all the available Active Record callbacks, listed in the sam
* `around_update`
* `after_update`
* `after_save`
+* `after_commit/after_rollback`
### Destroying an Object
* `before_destroy`
* `around_destroy`
* `after_destroy`
+* `after_commit/after_rollback`
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 4725e2c8a2..2a76df156c 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -707,7 +707,7 @@ You can additionally unscope specific where clauses. For example:
```ruby
Post.where(id: 10, trashed: false).unscope(where: :id)
-# => SELECT "posts".* FROM "posts" WHERE trashed = 0
+# SELECT "posts".* FROM "posts" WHERE trashed = 0
```
A relation which has used `unscope` will affect any relation it is
@@ -715,7 +715,7 @@ merged in to:
```ruby
Post.order('id asc').merge(Post.unscope(:order))
-# => SELECT "posts".* FROM "posts"
+# SELECT "posts".* FROM "posts"
```
### `only`
@@ -961,7 +961,7 @@ SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id =
WARNING: This method only works with `INNER JOIN`.
-Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method.
+Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method.
For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models:
@@ -1231,6 +1231,35 @@ Using a class method is the preferred way to accept arguments for scopes. These
category.posts.created_before(time)
```
+### Applying a default scope
+
+If we wish for a scope to be applied across all queries to the model we can use the
+`default_scope` method within the model itself.
+
+```ruby
+class Client < ActiveRecord::Base
+ default_scope { where("removed_at IS NULL") }
+end
+```
+
+When queries are executed on this model, the SQL query will now look something like
+this:
+
+```sql
+SELECT * FROM clients WHERE removed_at IS NULL
+```
+
+If you need to do more complex things with a default scope, you can alternatively
+define it as a class method:
+
+```ruby
+class Client < ActiveRecord::Base
+ def self.default_scope
+ # Should return an ActiveRecord::Relation.
+ end
+end
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
@@ -1242,26 +1271,26 @@ class User < ActiveRecord::Base
end
User.active.inactive
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
```
We can mix and match `scope` and `where` conditions and the final sql
-will have all conditions joined with `AND` .
+will have all conditions joined with `AND`.
```ruby
User.active.where(state: 'finished')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
```
If we do want the `last where clause` to win then `Relation#merge` can
-be used .
+be used.
```ruby
User.active.merge(User.inactive)
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
```
-One important caveat is that `default_scope` will be overridden by
+One important caveat is that `default_scope` will be prepended in
`scope` and `where` conditions.
```ruby
@@ -1272,48 +1301,18 @@ class User < ActiveRecord::Base
end
User.all
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
User.active
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
User.where(state: 'inactive')
-# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
```
-As you can see above the `default_scope` is being overridden by both
+As you can see above the `default_scope` is being merged in both
`scope` and `where` conditions.
-
-### Applying a default scope
-
-If we wish for a scope to be applied across all queries to the model we can use the
-`default_scope` method within the model itself.
-
-```ruby
-class Client < ActiveRecord::Base
- default_scope { where("removed_at IS NULL") }
-end
-```
-
-When queries are executed on this model, the SQL query will now look something like
-this:
-
-```sql
-SELECT * FROM clients WHERE removed_at IS NULL
-```
-
-If you need to do more complex things with a default scope, you can alternatively
-define it as a class method:
-
-```ruby
-class Client < ActiveRecord::Base
- def self.default_scope
- # Should return an ActiveRecord::Relation.
- end
-end
-```
-
### Removing All Scoping
If we wish to remove scoping for any reason we can use the `unscoped` method. This is
@@ -1338,11 +1337,6 @@ Client.unscoped {
Dynamic Finders
---------------
-NOTE: 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
-
For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods.
You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")`
@@ -1352,6 +1346,11 @@ If you want to find both by name and locked, you can chain these finders togethe
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`
@@ -1455,7 +1454,7 @@ If you'd like to use your own SQL to find records in a table you can use `find_b
```ruby
Client.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
- ORDER clients.created_at desc")
+ ORDER BY clients.created_at desc")
```
`find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index efa826e8df..a483a6dd24 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -575,7 +575,9 @@ This helper validates that the attribute's value is unique right before the
object gets saved. It does not create a uniqueness constraint in the database,
so it may happen that two different database connections create two records
with the same value for a column that you intend to be unique. To avoid that,
-you must create a unique index in your database.
+you must create a unique index on both columns in your database. See
+[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html)
+for more details about multiple column indexes.
```ruby
class Account < ActiveRecord::Base
@@ -616,10 +618,6 @@ The default error message is _"has already been taken"_.
This helper passes the record to a separate class for validation.
```ruby
-class Person < ActiveRecord::Base
- validates_with GoodnessValidator
-end
-
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if record.first_name == "Evil"
@@ -627,6 +625,10 @@ class GoodnessValidator < ActiveModel::Validator
end
end
end
+
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator
+end
```
NOTE: Errors added to `record.errors[:base]` relate to the state of the record
@@ -644,10 +646,6 @@ Like all other validations, `validates_with` takes the `:if`, `:unless` and
validator class as `options`:
```ruby
-class Person < ActiveRecord::Base
- validates_with GoodnessValidator, fields: [:first_name, :last_name]
-end
-
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
@@ -655,6 +653,10 @@ class GoodnessValidator < ActiveModel::Validator
end
end
end
+
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator, fields: [:first_name, :last_name]
+end
```
Note that the validator will be initialized *only once* for the whole application
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 59dfefd22f..5a4e15cfa9 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -157,9 +157,9 @@ Active Support provides `duplicable?` to programmatically query an object about
```ruby
"foo".duplicable? # => true
-"".duplicable? # => true
+"".duplicable? # => true
0.0.duplicable? # => false
-false.duplicable? # => false
+false.duplicable? # => false
```
By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects.
@@ -761,7 +761,7 @@ Arguments may be bare constant names:
Math.qualified_const_get("E") # => 2.718281828459045
```
-These methods are analogous to their builtin counterparts. In particular,
+These methods are analogous to their built-in counterparts. In particular,
`qualified_constant_defined?` accepts an optional second argument to be
able to say whether you want the predicate to look in the ancestors.
This flag is taken into account for each constant in the expression while
@@ -792,7 +792,7 @@ N.qualified_const_defined?("C::X") # => true
As the last example implies, the second argument defaults to true,
as in `const_defined?`.
-For coherence with the builtin methods only relative paths are accepted.
+For coherence with the built-in methods only relative paths are accepted.
Absolute qualified constant names like `::Math::PI` raise `NameError`.
NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`.
@@ -964,20 +964,7 @@ NOTE: Defined in `active_support/core_ext/module/delegation.rb`
There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
-The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API:
-
-```ruby
-redefine_method("#{reflection.name}=") do |new_value|
- association = association_instance_get(reflection.name)
-
- if association.nil? || association.target != new_value
- association = association_proxy_class.new(self, reflection)
- end
-
- association.replace(new_value)
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
-end
-```
+The method `redefine_method` prevents such a potential warning, removing the existing method before if needed.
NOTE: Defined in `active_support/core_ext/module/remove_method.rb`
@@ -1403,6 +1390,8 @@ The third argument, `indent_empty_lines`, is a flag that says whether empty line
The `indent!` method performs indentation in-place.
+NOTE: Defined in `active_support/core_ext/string/indent.rb`.
+
### Access
#### `at(position)`
@@ -1642,6 +1631,9 @@ Given a string with a qualified constant name, `demodulize` returns the very con
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
+"::Inflections".demodulize # => "Inflections"
+"".demodulize # => ""
+
```
Active Record for example uses this method to compute the name of a counter cache column:
@@ -2717,11 +2709,14 @@ The method `transform_keys` accepts a block and returns a hash that has applied
# => {"" => nil, "A" => :a, "1" => 1}
```
-The result in case of collision is undefined:
+In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
```ruby
{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase }
-# => {"A" => 2}, in my test, can't rely on this result though
+# The result could either be
+# => {"A"=>2}
+# or
+# => {"A"=>1}
```
This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions:
@@ -2756,11 +2751,14 @@ The method `stringify_keys` returns a hash that has a stringified version of the
# => {"" => nil, "a" => :a, "1" => 1}
```
-The result in case of collision is undefined:
+In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
```ruby
{"a" => 1, a: 2}.stringify_keys
-# => {"a" => 2}, in my test, can't rely on this result though
+# The result could either be
+# => {"a"=>2}
+# or
+# => {"a"=>1}
```
This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionView::Helpers::FormHelper` defines:
@@ -2797,11 +2795,14 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the
WARNING. Note in the previous example only one key was symbolized.
-The result in case of collision is undefined:
+In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
```ruby
{"a" => 1, a: 2}.symbolize_keys
-# => {:a=>2}, in my test, can't rely on this result though
+# The result could either be
+# => {:a=>2}
+# or
+# => {:a=>1}
```
This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionController::UrlRewriter` defines
@@ -2909,7 +2910,7 @@ NOTE: Defined in `active_support/core_ext/hash/indifferent_access.rb`.
### Compacting
-The methods `compact` and `compact!` return a Hash without items with `nil` value.
+The methods `compact` and `compact!` return a Hash without items with `nil` value.
```ruby
{a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2}
@@ -3836,7 +3837,7 @@ rescue NameError => e
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/name_error.rb`.
Extensions to `LoadError`
-------------------------
@@ -3859,4 +3860,4 @@ rescue NameError => e
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/load_error.rb`.
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 6c77a40d42..121cdc0199 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -457,6 +457,7 @@ Most times you only care about the data itself. Here is a shortcut to just get t
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
data = args.extract_options!
data # { extra: :information }
+end
```
You may also subscribe to events matching a regular expression. This enables you to subscribe to
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 311cc23cf0..261538d0be 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -128,6 +128,53 @@ On the other hand, regular comments do not use an arrow:
# polymorphic_url(record) # same as comment_url(record)
```
+Booleans
+--------
+
+In predicates and flags prefer documenting boolean semantics over exact values.
+
+When "true" or "false" are used as defined in Ruby use regular font. The
+singletons `true` and `false` need fixed-width font. Please avoid terms like
+"truthy", Ruby defines what is true and false in the language, and thus those
+words have a technical meaning and need no substitutes.
+
+As a rule of thumb, do not document singletons unless absolutely necessary. That
+prevents artificial constructs like `!!` or ternaries, allows refactors, and the
+code does not need to rely on the exact values returned by methods being called
+in the implementation.
+
+For example:
+
+```markdown
+`config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default
+```
+
+the user does not need to know which is the actual default value of the flag,
+and so we only document its boolean semantics.
+
+An example with a predicate:
+
+```ruby
+# Returns true if the collection is empty.
+#
+# If the collection has been loaded
+# it is equivalent to <tt>collection.size.zero?</tt>. If the
+# collection has not been loaded, it is equivalent to
+# <tt>collection.exists?</tt>. If the collection has not already been
+# loaded and you are going to fetch the records anyway it is better to
+# check <tt>collection.length.zero?</tt>.
+def empty?
+ if loaded?
+ size.zero?
+ else
+ @target.blank? && !scope.exists?
+ end
+end
+```
+
+The API is careful not to commit to any particular value, the method has
+predicate semantics, that's enough.
+
Filenames
---------
@@ -163,7 +210,17 @@ class Array
end
```
-WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc.
+WARNING: Using `+...+` for fixed-width font only works with simple content like
+ordinary method names, symbols, paths (with forward slashes), etc. Please use
+`<tt>...</tt>` for everything else, notably class or module names with a
+namespace as in `<tt>ActiveRecord::Base</tt>`.
+
+You can quickly test the RDoc output with the following command:
+
+```
+$ echo "+:to_param+" | rdoc --pipe
+#=> <p><code>:to_param</code></p>
+```
### Regular Font
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index bce5d6c55f..52fc9726d9 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -56,11 +56,11 @@ the comment operator on that line to later enable the asset pipeline:
To set asset compression methods, set the appropriate configuration options
in `production.rb` - `config.assets.css_compressor` for your CSS and
-`config.assets.js_compressor` for your Javascript:
+`config.assets.js_compressor` for your JavaScript:
```ruby
config.assets.css_compressor = :yui
-config.assets.js_compressor = :uglify
+config.assets.js_compressor = :uglifier
```
NOTE: The `sass-rails` gem is automatically used for CSS compression if included
@@ -245,7 +245,7 @@ When a file is referenced from a manifest or a helper, Sprockets searches the
three default asset locations for it.
The default locations are: the `images`, `javascripts` and `stylesheets`
-directories under the `apps/assets` folder, but these subdirectories
+directories under the `app/assets` folder, but these subdirectories
are not special - any path under `assets/*` will be searched.
For example, these files:
@@ -302,7 +302,7 @@ Sprockets uses files named `index` (with the relevant extensions) for a special
purpose.
For example, if you have a jQuery library with many modules, which is stored in
-`lib/assets/library_name`, the file `lib/assets/library_name/index.js` serves as
+`lib/assets/javascripts/library_name`, the file `lib/assets/javascripts/library_name/index.js` serves as
the manifest for all files in this library. This file could include a list of
all the required files in order, or a simple `require_tree` directive.
@@ -496,16 +496,11 @@ In this example, `require_self` is used. This puts the CSS contained within the
file (if any) at the precise location of the `require_self` call. If
`require_self` is called more than once, only the last call is respected.
-NOTE. If you want to use multiple Sass files, you should generally use the [Sass
-`@import`
-rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) instead
-of these Sprockets directives. Using Sprockets directives all Sass files exist
-within their own scope, making variables or mixins only available within the
-document they were defined in. You can do file globbing as well using
-`@import "*"`, and `@import "**/*"` to add the whole tree equivalent to how
-`require_tree` works. Check the [sass-rails
-documentation](https://github.com/rails/sass-rails#features) for more info and
-important caveats.
+NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import)
+instead of these Sprockets directives. Using Sprockets directives all Sass files exist within
+their own scope, making variables or mixins only available within the document they were defined in.
+You can do file globbing as well using `@import "*"`, and `@import "**/*"` to add the whole tree
+equivalent to how `require_tree` works. Check the [sass-rails documentation](https://github.com/rails/sass-rails#features) for more info and important caveats.
You can have as many manifest files as you need. For example, the `admin.css`
and `admin.js` manifest could contain the JS and CSS files that are used for the
@@ -586,23 +581,8 @@ runtime. To disable this behavior you can set:
config.assets.raise_runtime_errors = false
```
-When `raise_runtime_errors` is set to `false` sprockets will not check that dependencies of assets are declared properly. Here is a scenario where you must tell the asset pipeline about a dependency:
-
-If you have `application.css.erb` that references `logo.png` like this:
-
-```css
-#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
-```
-
-Then you must declare that `logo.png` is a dependency of `application.css.erb`, so when the image gets re-compiled, the css file does as well. You can do this using the `//= depend_on_asset` declaration:
-
-```css
-//= depend_on_asset "logo.png"
-#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
-```
-
-Without this declaration you may experience strange behavior when pushing to production that is difficult to debug. When you have `raise_runtime_errors` set to `true`, dependencies will be checked at runtime so you can ensure that all dependencies are met.
-
+When this option is true asset pipeline will check if all the assets loaded in your application
+are included in the `config.assets.precompile` list.
### Turning Debugging Off
@@ -729,17 +709,17 @@ JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and
`.scss` files are **not** automatically included as they compile to JS/CSS.
If you have other manifests or individual stylesheets and JavaScript files to
-include, you can add them to the `precompile` array in `config/application.rb`:
+include, you can add them to the `precompile` array in `config/initializers/assets.rb`:
```ruby
-config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
+Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
```
Or, you can opt to precompile all assets with something like this:
```ruby
-# config/application.rb
-config.assets.precompile << Proc.new do |path|
+# config/initializers/assets.rb
+Rails.application.config.assets.precompile << Proc.new do |path|
if path =~ /\.(css|js)\z/
full_path = Rails.application.assets.resolve(path).to_path
app_assets_path = Rails.root.join('app', 'assets').to_path
@@ -786,7 +766,7 @@ exception indicating the name of the missing file(s).
#### Far-future Expires Header
-Precompiled assets exist on the filesystem and are served directly by your web
+Precompiled assets exist on the file system and are served directly by your web
server. They do not have far-future headers by default, so to get the benefit of
fingerprinting you'll have to update your server configuration to add those
headers.
@@ -938,7 +918,7 @@ Customizing the Pipeline
### CSS Compression
-There is currently one option for compressing CSS, YUI. The [YUI CSS
+One of the options for compressing CSS is YUI. The [YUI CSS
compressor](http://yui.github.io/yuicompressor/css.html) provides
minification.
@@ -948,6 +928,11 @@ gem.
```ruby
config.assets.css_compressor = :yui
```
+The other option for compressing CSS if you have the sass-rails gem installed is
+
+```ruby
+config.assets.css_compressor = :sass
+```
### JavaScript Compression
@@ -1018,7 +1003,8 @@ The X-Sendfile header is a directive to the web server to ignore the response
from the application, and instead serve a specified file from disk. This option
is off by default, but can be enabled if your server supports it. When enabled,
this passes responsibility for serving the file to the web server, which is
-faster.
+faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file)
+on how to use this feature.
Apache and nginx support this option, which can be enabled in
`config/environments/production.rb`:
@@ -1033,6 +1019,10 @@ option, take care to paste this configuration option only into `production.rb`
and any other environments you define with production behavior (not
`application.rb`).
+TIP: For further details have a look at the docs of your production web server:
+- [Apache](https://tn123.org/mod_xsendfile/)
+- [Nginx](http://wiki.nginx.org/XSendfile)
+
Assets Cache Store
------------------
@@ -1051,6 +1041,14 @@ cache store.
config.assets.cache_store = :memory_store, { size: 32.megabytes }
```
+To disable the assets cache store:
+
+```ruby
+config.assets.configure do |env|
+ env.cache = ActiveSupport::Cache.lookup_store(:null_store)
+end
+```
+
Adding Assets to Your Gems
--------------------------
@@ -1145,7 +1143,7 @@ config.assets.digest = true
```
Rails 4 no longer sets default config values for Sprockets in `test.rb`, so
-`test.rb` now requies Sprockets configuration. The old defaults in the test
+`test.rb` now requires Sprockets configuration. The old defaults in the test
environment are: `config.assets.compile = true`, `config.assets.compress =
false`, `config.assets.debug = false` and `config.assets.digest = false`.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9867d2dc3f..df38bd7321 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -490,6 +490,19 @@ end
With this setup, you can retrieve `@employee.subordinates` and `@employee.manager`.
+In your migrations/schema, you will add a references column to the model itself.
+
+```ruby
+class CreateEmployees < ActiveRecord::Migration
+ def change
+ create_table :employees do |t|
+ t.references :manager
+ t.timestamps
+ end
+ end
+end
+```
+
Tips, Tricks, and Warnings
--------------------------
@@ -558,7 +571,7 @@ If you create an association some time after you build the underlying model, you
If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
-WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper\_boxes" and "papers" to generate a join table name of "papers\_paper\_boxes" because of the length of the name "paper\_boxes", but it in fact generates a join table name of "paper\_boxes\_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
+WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings).
Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 0d45e5fb28..b6423dd44e 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -30,13 +30,13 @@ config.action_controller.perform_caching = true
Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
-INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
+INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Action Caching
Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy.
-INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
+INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Fragment Caching
@@ -140,6 +140,26 @@ You can also combine the two schemes which is called "Russian Doll Caching":
It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment.
+### Low-Level Caching
+
+Sometimes you need to cache a particular value or query result, instead of caching view fragments. Rails caching mechanism works great for storing __any__ kind of information.
+
+The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned.
+
+Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching:
+
+```ruby
+class Product < ActiveRecord::Base
+ def competing_price
+ Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
+ Competitor::API.find_price(id)
+ end
+ end
+end
+```
+
+NOTE: Notice that in this example we used `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
+
### SQL Caching
Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 3b80faec7f..756c8f8b51 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -254,7 +254,7 @@ $ rails generate scaffold HighScore game:string score:integer
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything.
-The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
+The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
```bash
$ rake db:migrate
@@ -411,7 +411,7 @@ The `doc:` namespace has the tools to generate documentation for your app, API d
### `notes`
-`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.erb`, `.haml` and `.slim` for both default and custom annotations.
+`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
```bash
$ rake notes
@@ -425,6 +425,12 @@ app/models/school.rb:
* [ 17] [FIXME]
```
+You can add support for new file extensions using `config.annotations.register_extensions` option, which receives a list of the extensions with its corresponding regex to match it up.
+
+```ruby
+config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ }
+```
+
If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name.
```bash
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 412aecadd5..ae382fc54d 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -56,7 +56,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
end
```
-* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
+* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
@@ -110,7 +110,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`.
-* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
+* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode.
@@ -118,7 +118,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
-* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`.
+* `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.
@@ -274,7 +274,7 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
-* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc` for Rails, although Active Record defaults to `:local` when used outside of Rails.
+* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`.
* `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements.
@@ -292,6 +292,12 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true.
+* `config.active_record.dump_schema_after_migration` is a flag which
+ controls whether or not schema dump should happen (`db/schema.rb` or
+ `db/structure.sql`) when you run migrations. This is set to false in
+ `config/environments/production.rb` which is generated by Rails. The
+ default value is true if this configuration is not set.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
@@ -352,6 +358,10 @@ value. Defaults to `'encrypted cookie'`.
* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
+ method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
+ for more information. It defaults to true.
+
* `ActionDispatch::Callbacks.before` takes a block of code to run before the request.
* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`.
@@ -374,7 +384,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.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 View. Set to `nil` to disable logging.
-* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`. See the [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/) for more information.
+* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`, which turns on trimming of tail spaces and newline when using `<%= -%>` or `<%= =%>`. See the [Erubis documentation](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces) for more information.
* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true`
@@ -386,6 +396,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
The default setting is `true`, which uses the partial at `/admin/posts/_post.erb`. Setting the value to `false` would render `/posts/_post.erb`, which is the same behavior as rendering from a non-namespaced controller such as `PostsController`.
+* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
@@ -406,17 +418,25 @@ There are a number of settings available on `config.action_mailer`:
* `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true.
-* `config.action_mailer.delivery_method` defines the delivery method. The allowed values are `:smtp` (default), `:sendmail`, and `:test`.
+* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for more info.
* `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing.
* `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to:
```ruby
- :mime_version => "1.0",
- :charset => "UTF-8",
- :content_type => "text/plain",
- :parts_order => [ "text/plain", "text/enriched", "text/html" ]
+ mime_version: "1.0",
+ charset: "UTF-8",
+ content_type: "text/plain",
+ parts_order: ["text/plain", "text/enriched", "text/html"]
+ ```
+
+ Assign a hash to set additional options:
+
+ ```ruby
+ config.action_mailer.default_options = {
+ from: "noreply@example.com"
+ }
```
* `config.action_mailer.observers` registers observers which will be notified when mail is delivered.
@@ -441,6 +461,8 @@ There are a few configuration options available in Active Support:
* `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`.
+* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
+
* `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.
@@ -451,7 +473,6 @@ There are a few configuration options available in Active Support:
* `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings.
-* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
### Configuring a Database
@@ -554,18 +575,18 @@ $ rails runner 'puts ActiveRecord::Base.connections'
Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins.
-The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connectinon using the `"url"` sub key:
+The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connection using the `"url"` sub key:
```
$ cat config/database.yml
development:
- url: sqlite3://localhost/NOT_my_database
+ url: sqlite3:NOT_my_database
$ echo $DATABASE_URL
postgresql://localhost/my_database
$ rails runner 'puts ActiveRecord::Base.connections'
-{"development"=>{"adapter"=>"sqlite3", "host"=>"localhost", "database"=>"NOT_my_database"}}
+{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}}
```
Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name.
@@ -623,11 +644,9 @@ development:
encoding: unicode
database: blog_development
pool: 5
- username: blog
- password:
```
-Prepared Statements can be disabled thus:
+Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`:
```yaml
production:
@@ -635,6 +654,16 @@ production:
prepared_statements: false
```
+If enabled, Active Record will create up to `1000` prepared statements per database connection by default. To modify this behavior you can set `statement_limit` to a different value:
+
+```
+production:
+ adapter: postgresql
+ statement_limit: 200
+```
+
+The more prepared statements in use: the more memory your database will require. If your PostgreSQL database is hitting memory limits, try lowering `statement_limit` or disabling prepared statements.
+
#### Configuring an SQLite3 Database for JRuby Platform
If you choose to use SQLite3 and are using JRuby, your `config/database.yml` will look a little different. Here's the development section:
@@ -700,7 +729,7 @@ Rails will now prepend "/app1" when generating links.
#### Using Passenger
-Passenger makes it easiy to run your application in a subdirectory. You can find
+Passenger makes it easy to run your application in a subdirectory. You can find
the relevant configuration in the
[passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).
@@ -918,4 +947,4 @@ ActiveRecord::ConnectionTimeoutError - could not obtain a database connection wi
If you get the above error, you might want to increase the size of connection
pool by incrementing the `pool` option in `database.yml`
-NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
+NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 814237ba22..28e1172274 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -24,9 +24,9 @@ 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 was already reported. If you find no issue addressing it you can [add 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 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.)
-At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to 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.
+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.
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
@@ -50,7 +50,7 @@ Please don't put "feature request" items into GitHub Issues. If there's a new
feature that you want to see added to Ruby on Rails, you'll need to write the
code yourself - or convince someone else to partner with you to write the code.
Later in this guide you'll find detailed instructions for proposing a patch to
-Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you
+Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you
can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
@@ -69,89 +69,6 @@ won't be accepted." But it's the proper place to discuss new ideas. GitHub
Issues are not a particularly good venue for the sometimes long and involved
discussions new features require.
-Setting Up a Development Environment
-------------------------------------
-
-To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer.
-
-### The Easy Way
-
-The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box).
-
-### The Hard Way
-
-In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html).
-
-
-Running an Application Against Your Local Branch
-------------------------------------------------
-
-The `--dev` flag of `rails new` generates an application that uses your local
-branch:
-
-```bash
-$ cd rails
-$ bundle exec rails new ~/my-test-app --dev
-```
-
-The application generated in `~/my-test-app` runs against your local branch
-and in particular sees any modifications upon server reboot.
-
-
-Testing Active Record
----------------------
-
-This is how you run the Active Record test suite only for SQLite3:
-
-```bash
-$ cd activerecord
-$ bundle exec rake test_sqlite3
-```
-
-You can now run the tests as you did for `sqlite3`. The tasks are respectively
-
-```bash
-test_mysql
-test_mysql2
-test_postgresql
-```
-
-Finally,
-
-```bash
-$ bundle exec rake test
-```
-
-will now run the four of them in turn.
-
-You can also run any single test separately:
-
-```bash
-$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
-```
-
-You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server.
-
-### Warnings
-
-The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-
-As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
-
-```bash
-$ RUBYOPT=-W0 bundle exec rake test
-```
-
-### Older Versions of Ruby on Rails
-
-If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch:
-
-```bash
-$ git branch --track 3-0-stable origin/3-0-stable
-$ git checkout 3-0-stable
-```
-
-TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with.
Helping to Resolve Existing Issues
----------------------------------
@@ -201,7 +118,8 @@ If your comment simply says "+1", then odds are that other reviewers aren't goin
Contributing to the Rails Documentation
---------------------------------------
-Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference.
+Ruby on Rails has two main sets of documentation: the guides, which help you
+learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides).
@@ -226,9 +144,21 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n
Contributing to the Rails Code
------------------------------
-### Clone the Rails Repository
+### Setting Up a Development Environment ###
+
+To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer.
+
+#### The Easy Way
+
+The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box).
+
+#### The Hard Way
+
+In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html).
-The first thing you need to do to be able to contribute code is to clone the repository:
+### Clone the Rails Repository ###
+
+To be able to contribute code, you need to clone the Rails repository:
```bash
$ git clone git://github.com/rails/rails.git
@@ -243,29 +173,31 @@ $ git checkout -b my_new_branch
It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
-### Write Your Code
+### Running an Application Against Your Local Branch ###
+
+In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch:
-Now get busy and add or edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind:
+```bash
+$ cd rails
+$ bundle exec rails new ~/my-test-app --dev
+```
+
+The application generated in `~/my-test-app` runs against your local branch
+and in particular sees any modifications upon server reboot.
+
+### Write Your Code ###
+
+Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind:
* Get the code right.
* Use Rails idioms and helpers.
* Include tests that fail without your code, and pass with it.
* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
-It is not customary in Rails to run the full test suite before pushing
-changes. The railties test suite in particular takes a long time, and even
-more if the source code is mounted in `/vagrant` as happens in the recommended
-workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
-
-As a compromise, test what your code obviously affects, and if the change is
-not in railties run the whole test suite of the affected component. If all is
-green that's enough to propose your contribution. We have [Travis CI](https://travis-ci.org/rails/rails)
-as a safety net for catching unexpected breakages
-elsewhere.
TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted.
-### Follow the Coding Conventions
+#### Follow the Coding Conventions
Rails follows a simple set of coding style conventions:
@@ -283,13 +215,90 @@ Rails follows a simple set of coding style conventions:
The above are guidelines - please use your best judgment in using them.
-### Updating the CHANGELOG
+### Running Tests ###
+It is not customary in Rails to run the full test suite before pushing
+changes. The railties test suite in particular takes a long time, and even
+more if the source code is mounted in `/vagrant` as happens in the recommended
+workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
+
+As a compromise, test what your code obviously affects, and if the change is
+not in railties, run the whole test suite of the affected component. If all
+tests are passing, that's enough to propose your contribution. We have
+[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching
+unexpected breakages elsewhere.
+
+#### Entire Rails:
+To run all the tests, do:
+```bash
+$ cd rails
+$ bundle exec rake test
+```
+#### Particular component of Rails
+To run tests only for particular component(ActionPack, ActiveRecord, etc.). For
+example, to run `ActionMailer` tests you can:
+
+```bash
+$ cd actionmailer
+$ bundle exec rake test
+```
+
+##### Testing Active Record
+
+This is how you run the Active Record test suite only for SQLite3:
+
+```bash
+$ cd activerecord
+$ bundle exec rake test_sqlite3
+```
+
+You can now run the tests as you did for `sqlite3`. The tasks are respectively
+
+```bash
+test_mysql
+test_mysql2
+test_postgresql
+```
+
+Finally,
+
+```bash
+$ bundle exec rake test
+```
+
+will now run the four of them in turn.
+
+You can also run any single test separately:
+
+```bash
+$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+```
+
+You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server.
+
+#### Single Test separately
+to run just one test. For example, to run `LayoutMailerTest` you can:
+
+```bash
+$ cd actionmailer
+$ ruby -w -Ilib:test test/mail_layout_test.rb
+```
+
+### Warnings ###
+
+The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
+
+If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+
+```bash
+$ RUBYOPT=-W0 bundle exec rake test
+```
+### Updating the CHANGELOG ###
The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
-A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
+A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry:
```
* Summary of a change that briefly describes what was changed. You can use multiple
@@ -308,17 +317,17 @@ A CHANGELOG entry should summarize what was changed and should end with author's
Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
-### Sanity Check
+### Sanity Check ###
You should not be the only person who looks at the code before you submit it.
If you know someone else who uses Rails, try asking them if they'll check out
your work. If you don't know anyone else using Rails, try hopping into the IRC
-room or posting about your idea to the rails-core mailing list. Doing this in
-private before you push a patch out publicly is the “smoke test” for a patch:
-if you can’t convince one other developer of the beauty of your code, you’re
+room or posting about your idea to the rails-core mailing list. Doing this in
+private before you push a patch out publicly is the "smoke test" for a patch:
+if you can't convince one other developer of the beauty of your code, you’re
unlikely to convince the core team either.
-### Commit Your Changes
+### Commit Your Changes ###
When you're happy with the code on your computer, you need to commit the changes to Git:
@@ -358,7 +367,7 @@ You can also add bullet points:
TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean.
-### Update Your Branch
+### Update Your Branch ###
It's pretty likely that other changes to master have happened while you were working. Go get them:
@@ -376,7 +385,7 @@ $ git rebase master
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
-### Fork
+### Fork ###
Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and press "Fork" in the upper right hand corner.
@@ -466,11 +475,11 @@ the same way that you appreciate feedback on your patches.
### Iterate as Necessary
-It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem.
+It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into the knowledge of the community. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem.
#### Squashing commits
-One of the things that we may ask you to do is "squash your commits," which
+One of the things that we may ask you to do is to "squash your commits", which
will combine all of your commits into a single commit. We prefer pull requests
that are a single commit. This makes it easier to backport changes to stable
branches, squashing makes it easier to revert bad commits, and the git history
@@ -506,7 +515,19 @@ $ git push origin my_pull_request -f
You should be able to refresh the pull request on GitHub and see that it has
been updated.
-### Backporting
+
+### Older Versions of Ruby on Rails ###
+
+If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch:
+
+```bash
+$ git branch --track 3-0-stable origin/3-0-stable
+$ git checkout 3-0-stable
+```
+
+TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with.
+
+#### Backporting
Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort.
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 7c6858fa2c..8767fbecce 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Pratik Naik', 'lifo') do %>
- Pratik Naik is a Ruby on Rails developer at <a href="http://www.37signals.com">37signals</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
+ Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
<% end %>
<%= author('Emilio Tagua', 'miloops') do %>
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 226137c89a..b067d9efb7 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -123,7 +123,7 @@ config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")
```
-TIP: By default, each log is created under `Rails.root/log/` and the log file name is `environment_name.log`.
+TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running.
### Log Levels
@@ -210,7 +210,7 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "
```
### Impact of Logs on Performance
-Logging will always have a small impact on performance of your rails app,
+Logging will always have a small impact on performance of your rails app,
particularly when logging to disk.However, there are a few subtleties:
Using the `:debug` level will have a greater performance penalty than `:fatal`,
@@ -224,446 +224,576 @@ Another potential pitfall is that if you have many calls to `Logger` like this
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
```
-In the above example, There will be a performance impact even if the allowed
-output level doesn't include debug. The reason is that Ruby has to evaluate
-these strings, which includes instantiating the somewhat heavy `String` object
+In the above example, There will be a performance impact even if the allowed
+output level doesn't include debug. The reason is that Ruby has to evaluate
+these strings, which includes instantiating the somewhat heavy `String` object
and interpolating the variables, and which takes time.
-Therefore, it's recommended to pass blocks to the logger methods, as these are
-only evaluated if the output level is the same or included in the allowed level
+Therefore, it's recommended to pass blocks to the logger methods, as these are
+only evaluated if the output level is the same or included in the allowed level
(i.e. lazy loading). The same code rewritten would be:
```ruby
logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
-The contents of the block, and therefore the string interpolation, is only
-evaluated if debug is enabled. This performance savings is only really
+The contents of the block, and therefore the string interpolation, is only
+evaluated if debug is enabled. This performance savings is only really
noticeable with large amounts of logging, but it's a good practice to employ.
-Debugging with the `debugger` gem
+Debugging with the `byebug` gem
---------------------------------
-When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.
+When your code is behaving in unexpected ways, you can try printing to logs or
+the console to diagnose the problem. Unfortunately, there are times when this
+sort of error tracking is not effective in finding the root cause of a problem.
+When you actually need to journey into your running source code, the debugger
+is your best companion.
-The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written deeper into Rails code.
+The debugger can also help you if you want to learn about the Rails source code
+but don't know where to start. Just debug any request to your application and
+use this guide to learn how to move from the code you have written deeper into
+Rails code.
### Setup
-You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run:
+You can use the `byebug` gem to set breakpoints and step through live code in
+Rails. To install it, just run:
```bash
-$ gem install debugger
+$ gem install byebug
```
-Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the `debugger` method.
+Inside any Rails application you can then invoke the debugger by calling the
+`byebug` method.
Here's an example:
```ruby
class PeopleController < ApplicationController
def new
- debugger
+ byebug
@person = Person.new
end
end
```
-If you see this message in the console or logs:
+### The Shell
+
+As soon as your application calls the `byebug` method, the debugger will be
+started in a debugger shell inside the terminal window where you launched your
+application server, and you will be placed at the debugger's prompt `(byebug)`.
+Before the prompt, the code around the line that is about to be run will be
+displayed and the current line will be marked by '=>'. Like this:
```
-***** Debugger requested, but was not available: Start server with --debugger to enable *****
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
+
+(byebug)
```
-Make sure you have started your web server with the option `--debugger`:
+If you got there by a browser request, the browser tab containing the request
+will be hung until the debugger has finished and the trace has finished
+processing the entire request.
+
+For example:
```bash
-$ rails server --debugger
=> Booting WEBrick
-=> Rails 4.0.0 application starting on http://0.0.0.0:3000
-=> Debugger enabled
-...
-```
+=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000
+=> Run `rails server -h` for more startup options
+=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
+=> Ctrl-C to shutdown server
+[2014-04-11 13:11:47] INFO WEBrick 1.3.1
+[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux]
+[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
-TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`.
-### The Shell
+Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
+ ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
+Processing by PostsController#index as HTML
-As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(rdb:n)`. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
-If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request.
+(byebug)
+```
-For example:
+Now it's time to explore and dig into your application. A good place to start is
+by asking the debugger for help. Type: `help`
-```bash
-@posts = Post.all
-(rdb:7)
```
+(byebug) help
-Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help`
+byebug 2.7.0
-```
-(rdb:7) help
-ruby-debug help v0.10.2
Type 'help <command-name>' for help on a specific command
Available commands:
-backtrace delete enable help next quit show trace
-break disable eval info p reload source undisplay
-catch display exit irb pp restart step up
-condition down finish list ps save thread var
-continue edit frame method putl set tmate where
+backtrace delete enable help list pry next restart source up
+break disable eval info method ps save step var
+catch display exit interrupt next putl set thread
+condition down finish irb p quit show trace
+continue edit frame kill pp reload skip undisplay
```
-TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_
-
-The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command.
+TIP: To view the help menu for any command use `help <command-name>` at the
+debugger prompt. For example: _`help list`_. You can abbreviate any debugging
+command by supplying just enough letters to distinguish them from other
+commands, so you can also use `l` for the `list` command, for example.
-This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by `=>`.
+To see the previous ten lines you should type `list-` (or `l-`)
```
-(rdb:7) list
+(byebug) l-
+
[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
-=> 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
-```
+ 2 before_action :set_post, only: [:show, :edit, :update, :destroy]
+ 3
+ 4 # GET /posts
+ 5 # GET /posts.json
+ 6 def index
+ 7 byebug
+ 8 @posts = Post.find_recent
+ 9
+ 10 respond_to do |format|
-If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out.
-
-```
-(rdb:7) l
-[11, 20] in /PathTo/project/app/controllers/posts_controller.rb
- 11 end
- 12 end
- 13
- 14 # GET /posts/1
- 15 # GET /posts/1.json
- 16 def show
- 17 @post = Post.find(params[:id])
- 18
- 19 respond_to do |format|
- 20 format.html # show.html.erb
```
-And so on until the end of the current file. When the end of file is reached, the `list` command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer.
+This way you can move inside the file, being able to see the code above and over
+the line where you added the `byebug` call. Finally, to see where you are in
+the code again you can type `list=`
-On the other hand, to see the previous ten lines you should type `list-` (or `l-`)
-
-```
-(rdb:7) l-
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
- 1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
- 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
```
+(byebug) list=
-This way you can move inside the file, being able to see the code above and over the line you added the `debugger`.
-Finally, to see where you are in the code again you can type `list=`
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
-```
-(rdb:7) list=
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
- 1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
-=> 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
+(byebug)
```
### The Context
-When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack.
-
-The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
-
-At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then `backtrace` will supply the answer.
-
-```
-(rdb:5) where
- #0 PostsController.index
- at line /PathTo/project/app/controllers/posts_controller.rb:6
- #1 Kernel.send
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
- #2 ActionController::Base.perform_action_without_filters
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
- #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...)
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617
+When you start debugging your application, you will be placed in different
+contexts as you go through the different parts of the stack.
+
+The debugger creates a context when a stopping point or an event is reached. The
+context has information about the suspended program which enables the debugger
+to inspect the frame stack, evaluate variables from the perspective of the
+debugged program, and contains information about the place where the debugged
+program is stopped.
+
+At any time you can call the `backtrace` command (or its alias `where`) to print
+the backtrace of the application. This can be very helpful to know how you got
+where you are. If you ever wondered about how you got somewhere in your code,
+then `backtrace` will supply the answer.
+
+```
+(byebug) where
+--> #0 PostsController.index
+ at /PathTo/project/test_app/app/controllers/posts_controller.rb:8
+ #1 ActionController::ImplicitRender.send_action(method#String, *args#Array)
+ at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/implicit_render.rb:4
+ #2 AbstractController::Base.process_action(action#NilClass, *args#Array)
+ at /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb:189
+ #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
+ at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/rendering.rb:10
...
```
-You move anywhere you want in this trace (thus changing the context) by using the `frame _n_` command, where _n_ is the specified frame number.
+The current frame is marked with `-->`. You can move anywhere you want in this
+trace (thus changing the context) by using the `frame _n_` command, where _n_ is
+the specified frame number. If you do that, `byebug` will display your new
+context.
```
-(rdb:5) frame 2
-#2 ActionController::Base.perform_action_without_filters
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
+(byebug) frame 2
+
+[184, 193] in /PathToGems/actionpack-4.1.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
+ 187: # which is *not* necessarily the same as the action name.
+ 188: def process_action(method_name, *args)
+=> 189: send_action(method_name, *args)
+ 190: end
+ 191:
+ 192: # Actually call the method associated with the action. Override
+ 193: # this method if you wish to change how action methods are called,
+
+(byebug)
```
-The available variables are the same as if you were running the code line by line. After all, that's what debugging is.
+The available variables are the same as if you were running the code line by
+line. After all, that's what debugging is.
-Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one. Up in this case is towards higher-numbered stack frames, and down is towards lower-numbered stack frames.
+You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order
+to change the context _n_ frames up or down the stack respectively. _n_ defaults
+to one. Up in this case is towards higher-numbered stack frames, and down is
+towards lower-numbered stack frames.
### Threads
-The debugger can list, stop, resume and switch between running threads by using the command `thread` (or the abbreviated `th`). This command has a handful of options:
+The debugger can list, stop, resume and switch between running threads by using
+the `thread` command (or the abbreviated `th`). This command has a handful of
+options:
* `thread` shows the current thread.
-* `thread list` is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution.
+* `thread list` is used to list all threads and their statuses. The plus +
+character and the number indicates the current thread of execution.
* `thread stop _n_` stop thread _n_.
* `thread resume _n_` resumes thread _n_.
* `thread switch _n_` switches the current thread context to _n_.
-This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code.
+This command is very helpful, among other occasions, when you are debugging
+concurrent threads and need to verify that there are no race conditions in your
+code.
### Inspecting Variables
-Any expression can be evaluated in the current context. To evaluate an expression, just type it!
-
-This example shows how you can print the instance_variables defined within the current context:
-
-```
-@posts = Post.all
-(rdb:11) instance_variables
-["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"]
-```
-
-As you may have figured out, all of the variables that you can access from a controller are displayed. This list is dynamically updated as you execute code. For example, run the next line using `next` (you'll learn more about this command later in this guide).
-
-```
-(rdb:11) next
-Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET]
- Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e
- Parameters: {"action"=>"index", "controller"=>"posts"}
-/PathToProject/posts_controller.rb:8
-respond_to do |format|
+Any expression can be evaluated in the current context. To evaluate an
+expression, just type it!
+
+This example shows how you can print the instance variables defined within the
+current context:
+
+```
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
+
+(byebug) instance_variables
+[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
+ :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response_body, :@marked_for_same_origin_verification, :@_config]
+```
+
+As you may have figured out, all of the variables that you can access from a
+controller are displayed. This list is dynamically updated as you execute code.
+For example, run the next line using `next` (you'll learn more about this
+command later in this guide).
+
+```
+(byebug) next
+[5, 14] in /PathTo/project/app/controllers/posts_controller.rb
+ 5 # GET /posts.json
+ 6 def index
+ 7 byebug
+ 8 @posts = Post.find_recent
+ 9
+=> 10 respond_to do |format|
+ 11 format.html # index.html.erb
+ 12 format.json { render json: @posts }
+ 13 end
+ 14 end
+ 15
+(byebug)
```
And then ask again for the instance_variables:
```
-(rdb:11) instance_variables.include? "@posts"
+(byebug) instance_variables.include? "@posts"
true
```
-Now `@posts` is included in the instance variables, because the line defining it was executed.
+Now `@posts` is included in the instance variables, because the line defining it
+was executed.
-TIP: You can also step into **irb** mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But be warned: this is an experimental feature.
+TIP: You can also step into **irb** mode with the command `irb` (of course!).
+This way an irb session will be started within the context you invoked it. But
+be warned: this is an experimental feature.
-The `var` method is the most convenient way to show variables and their values:
+The `var` method is the most convenient way to show variables and their values.
+Let's let `byebug` to help us with it.
```
-var
-(rdb:1) v[ar] const <object> show constants of object
-(rdb:1) v[ar] g[lobal] show global variables
-(rdb:1) v[ar] i[nstance] <object> show instance variables of object
-(rdb:1) v[ar] l[ocal] show local variables
+(byebug) help var
+v[ar] cl[ass] show class variables of self
+v[ar] const <object> show constants of object
+v[ar] g[lobal] show global variables
+v[ar] i[nstance] <object> show instance variables of object
+v[ar] l[ocal] show local variables
```
-This is a great way to inspect the values of the current context variables. For example:
+This is a great way to inspect the values of the current context variables. For
+example, to check that we have no local variables currently defined.
```
-(rdb:9) var local
- __dbg_verbose_save => false
+(byebug) var local
+(byebug)
```
You can also inspect for an object method this way:
```
-(rdb:9) var instance Post.new
-@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"...
+(byebug) var instance Post.new
+@_start_transaction_state = {}
+@aggregation_cache = {}
+@association_cache = {}
+@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil}
@attributes_cache = {}
-@new_record = true
+@changed_attributes = nil
+...
```
-TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console.
+TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate
+Ruby expressions and display the value of variables to the console.
-You can use also `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on.
+You can use also `display` to start watching variables. This is a good way of
+tracking the values of a variable while the execution goes on.
```
-(rdb:1) display @recent_comments
-1: @recent_comments =
+(byebug) display @posts
+1: @posts = nil
```
-The variables inside the displaying list will be printed with their values after you move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example).
+The variables inside the displaying list will be printed with their values after
+you move in the stack. To stop displaying a variable use `undisplay _n_` where
+_n_ is the variable number (1 in the last example).
### Step by Step
-Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution.
+Now you should know where you are in the running trace and be able to print the
+available variables. But lets continue and move on with the application
+execution.
-Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to the debugger.
+Use `step` (abbreviated `s`) to continue running your program until the next
+logical stopping point and return control to the debugger.
-TIP: You can also use `step+ n` and `step- n` to move forward or backward `n` steps respectively.
+You may also use `next` which is similar to step, but function or method calls
+that appear within the line of code are executed without stopping.
-You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps.
+TIP: You can also use `step n` or `next n` to move forwards `n` steps at once.
-The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just a single step, while `next` moves to the next line without descending inside methods.
+The difference between `next` and `step` is that `step` stops at the next line
+of code executed, doing just a single step, while `next` moves to the next line
+without descending inside methods.
-For example, consider this block of code with an included `debugger` statement:
+For example, consider the following situation:
```ruby
-class Author < ActiveRecord::Base
- has_one :editorial
- has_many :comments
+Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
+Processing by PostsController#index as HTML
- def find_recent_comments(limit = 10)
- debugger
- @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
- end
-end
+[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb
+ 1: class Post < ActiveRecord::Base
+ 2:
+ 3: def self.find_recent(limit = 10)
+ 4: byebug
+=> 5: where('created_at > ?', 1.week.ago).limit(limit)
+ 6: end
+ 7:
+ 8: end
+
+(byebug)
```
-TIP: You can use the debugger while using `rails console`. Just remember to `require "debugger"` before calling the `debugger` method.
+If we use `next`, we want go deep inside method calls. Instead, byebug will go
+to the next line within the same context. In this case, this is the last line of
+the method, so `byebug` will jump to next next line of the previous frame.
```
-$ rails console
-Loading development environment (Rails 4.0.0)
->> require "debugger"
-=> []
->> author = Author.first
-=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
->> author.find_recent_comments
-/PathTo/project/app/models/author.rb:11
-)
-```
+(byebug) next
+Next went up a frame because previous frame finished
-With the code stopped, take a look around:
+[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: @posts = Post.find_recent
+ 8:
+=> 9: respond_to do |format|
+ 10: format.html # index.html.erb
+ 11: format.json { render json: @posts }
+ 12: end
+ 13: end
-```
-(rdb:1) list
-[2, 9] in /PathTo/project/app/models/author.rb
- 2 has_one :editorial
- 3 has_many :comments
- 4
- 5 def find_recent_comments(limit = 10)
- 6 debugger
-=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
- 8 end
- 9 end
+(byebug)
```
-You are at the end of the line, but... was this line executed? You can inspect the instance variables.
+If we use `step` in the same situation, we will literally go the next ruby
+instruction to be executed. In this case, the activesupport's `week` method.
```
-(rdb:1) var instance
-@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
-@attributes_cache = {}
-```
+(byebug) step
-`@recent_comments` hasn't been defined yet, so it's clear that this line hasn't been executed yet. Use the `next` command to move on in the code:
+[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb
+ 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
+ 51: end
+ 52: alias :day :days
+ 53:
+ 54: def weeks
+=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ 56: end
+ 57: alias :week :weeks
+ 58:
+ 59: def fortnights
+(byebug)
```
-(rdb:1) next
-/PathTo/project/app/models/author.rb:12
-@recent_comments
-(rdb:1) var instance
-@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
-@attributes_cache = {}
-@comments = []
-@recent_comments = []
-```
-
-Now you can see that the `@comments` relationship was loaded and @recent_comments defined because the line was executed.
-If you want to go deeper into the stack trace you can move single `steps`, through your calling methods and into Rails code. This is one of the best ways to find bugs in your code, or perhaps in Ruby or Rails.
+This is one of the best ways to find bugs in your code, or perhaps in Ruby on
+Rails.
### Breakpoints
-A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line.
+A breakpoint makes your application stop whenever a certain point in the program
+is reached. The debugger shell is invoked in that line.
-You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually:
+You can add breakpoints dynamically with the command `break` (or just `b`).
+There are 3 possible ways of adding breakpoints manually:
* `break line`: set breakpoint in the _line_ in the current source file.
-* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger.
-* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line.
+* `break file:line [if expression]`: set breakpoint in the _line_ number inside
+the _file_. If an _expression_ is given it must evaluated to _true_ to fire up
+the debugger.
+* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and
+\# for class and instance method respectively) defined in _class_. The
+_expression_ works the same way as with file:line.
+
+
+For example, in the previous situation
```
-(rdb:5) break 10
-Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10
+[4, 13] in /PathTo/project/app/controllers/posts_controller.rb
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: @posts = Post.find_recent
+ 8:
+=> 9: respond_to do |format|
+ 10: format.html # index.html.erb
+ 11: format.json { render json: @posts }
+ 12: end
+ 13: end
+
+(byebug) break 11
+Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11
+
```
-Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
+Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you
+supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
```
-(rdb:5) info breakpoints
+(byebug) info breakpoints
Num Enb What
- 1 y at filters.rb:10
+1 y at /PathTo/project/app/controllers/posts_controller.rb:11
```
-To delete breakpoints: use the command `delete _n_` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active..
+To delete breakpoints: use the command `delete _n_` to remove the breakpoint
+number _n_. If no number is specified, it deletes all breakpoints that are
+currently active.
```
-(rdb:5) delete 1
-(rdb:5) info breakpoints
+(byebug) delete 1
+(byebug) info breakpoints
No breakpoints.
```
You can also enable or disable breakpoints:
-* `enable breakpoints`: allow a list _breakpoints_ or all of them if no list is specified, to stop your program. This is the default state when you create a breakpoint.
+* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is
+specified, to stop your program. This is the default state when you create a
+breakpoint.
* `disable breakpoints`: the _breakpoints_ will have no effect on your program.
### Catching Exceptions
-The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it.
+The command `catch exception-name` (or just `cat exception-name`) can be used to
+intercept an exception of type _exception-name_ when there would otherwise be no
+handler for it.
To list all active catchpoints use `catch`.
### Resuming Execution
-There are two ways to resume execution of an application that is stopped in the debugger:
-
-* `continue` [line-specification] \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached.
-* `finish` [frame-number] \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns.
+There are two ways to resume execution of an application that is stopped in the
+debugger:
+
+* `continue` [line-specification] \(or `c`): resume program execution, at the
+address where your script last stopped; any breakpoints set at that address are
+bypassed. The optional argument line-specification allows you to specify a line
+number to set a one-time breakpoint which is deleted when that breakpoint is
+reached.
+* `finish` [frame-number] \(or `fin`): execute until the selected stack frame
+returns. If no frame number is given, the application will run until the
+currently selected frame returns. The currently selected frame starts out the
+most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been
+performed. If a frame number is given it will run until the specified frame
+returns.
### Editing
Two commands allow you to open code from the debugger into an editor:
-* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given.
-* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified.
+* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR
+environment variable. A specific _line_ can also be given.
### Quitting
-To exit the debugger, use the `quit` command (abbreviated `q`), or its alias `exit`.
+To exit the debugger, use the `quit` command (abbreviated `q`), or its alias
+`exit`.
-A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again.
+A simple quit tries to terminate all threads in effect. Therefore your server
+will be stopped and you will have to start it again.
### Settings
-The `debugger` gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options:
-
-* `set reload`: Reload source code when changed.
-* `set autolist`: Execute `list` command on every breakpoint.
-* `set listsize _n_`: Set number of source lines to list by default to _n_.
-* `set forcestep`: Make sure the `next` and `step` commands always move to a new line
+`byebug` has a few available options to tweak its behaviour:
-You can see the full list by using `help set`. Use `help set _subcommand_` to learn about a particular `set` command.
+* `set autoreload`: Reload source code when changed (default: true).
+* `set autolist`: Execute `list` command on every breakpoint (default: true).
+* `set listsize _n_`: Set number of source lines to list by default to _n_
+(default: 10)
+* `set forcestep`: Make sure the `next` and `step` commands always move to a new
+line.
-TIP: You can save these settings in an `.rdebugrc` file in your home directory. The debugger reads these global settings when it starts.
+You can see the full list by using `help set`. Use `help set _subcommand_` to
+learn about a particular `set` command.
-Here's a good start for an `.rdebugrc`:
+TIP: You can save these settings in an `.byebugrc` file in your home directory.
+The debugger reads these global settings when it starts. For example:
```bash
-set autolist
set forcestep
set listsize 25
```
@@ -671,35 +801,59 @@ set listsize 25
Debugging Memory Leaks
----------------------
-A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level.
+A Ruby application (on Rails or not), can leak memory - either in the Ruby code
+or at the C code level.
-In this section, you will learn how to find and fix such leaks by using tool such as Valgrind.
+In this section, you will learn how to find and fix such leaks by using tool
+such as Valgrind.
### Valgrind
-[Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions.
+[Valgrind](http://valgrind.org/) is a Linux-only application for detecting
+C-based memory leaks and race conditions.
-There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates.
+There are Valgrind tools that can automatically detect many memory management
+and threading bugs, and profile your programs in detail. For example, if a C
+extension in the interpreter calls `malloc()` but doesn't properly call
+`free()`, this memory won't be available until the app terminates.
-For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver.
+For further information on how to install Valgrind and use with Ruby, refer to
+[Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/)
+by Evan Weaver.
Plugins for Debugging
---------------------
-There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:
-
-* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate.
-* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs.
-* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
-* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
-* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection.
-* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more.
+There are some Rails plugins to help you to find errors and debug your
+application. Here is a list of useful plugins for debugging:
+
+* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has
+footnotes that give request information and link back to your source via
+TextMate.
+* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query
+origin tracing to your logs.
+* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin
+not only runs "EXPLAIN" before each of your select queries in development, but
+provides a small DIV in the rendered output of each page with the summary of
+warnings for each query that it analyzed.
+* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master)
+Provides a mailer object and a default set of templates for sending email
+notifications when errors occur in a Rails application.
+* [Better Errors](https://github.com/charliesome/better_errors) Replaces the
+standard Rails error page with a new one containing more contextual information,
+like source code and variable inspection.
+* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails
+development that will end your tailing of development.log. Have all information
+about your Rails app requests in the browser - in the Developer Tools panel.
+Provides insight to db/rendering/total times, parameter list, rendered views and
+more.
References
----------
* [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html)
* [debugger Homepage](https://github.com/cldwalker/debugger)
+* [byebug Homepage](https://github.com/deivid-rodriguez/byebug)
* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/)
* [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised)
* [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace)
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 4ee43b6a97..b0e070120d 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -117,7 +117,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby
NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running.
-You can use homebrew to install memcached on OSX:
+You can use [Homebrew](http://brew.sh/) to install memcached on OSX:
```bash
$ brew install memcached
@@ -210,6 +210,14 @@ FreeBSD users will have to run the following:
# pkg_add -r postgresql92-client postgresql92-server
```
+You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX:
+
+```bash
+$ brew install mysql
+$ brew install postgresql
+```
+Follow instructions given by [Homebrew](http://brew.sh/) to start these.
+
Or install them through ports (they are located under the `databases` folder).
If you run into troubles during the installation of MySQL, please see
[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html).
@@ -245,10 +253,15 @@ $ bundle exec rake mysql:build_databases
```
PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account
+This is not needed when installed via [Homebrew](http://brew.sh).
```bash
$ sudo -u postgres createuser --superuser $USER
```
+And for OS X (when installed via [Homebrew](http://brew.sh))
+```bash
+$ createuser --superuser $USER
+```
and then create the test databases with
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index ae47744e31..a160c462b2 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -117,7 +117,7 @@
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 3.1
+ description: This guide explains the internals of the Rails initialization process as of Rails 4
-
name: Extending Rails
documents:
@@ -167,7 +167,6 @@
-
name: Ruby on Rails 4.1 Release Notes
url: 4_1_release_notes.html
- work_in_progress: true
description: Release notes for Rails 4.1.
-
name: Ruby on Rails 4.0 Release Notes
diff --git a/guides/source/engines.md b/guides/source/engines.md
index bbd63bb892..8f9ba0995f 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1052,6 +1052,16 @@ 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.
+
Improving engine functionality
------------------------------
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 455dc7bebe..027b6303fc 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,16 +1,16 @@
Form Helpers
============
-Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails does away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
+Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view helpers for generating form markup. However, since these helpers have different use cases, developers need to know the differences between the helper methods before putting them to use.
After reading this guide, you will know:
* How to create search forms and similar kind of generic forms not representing any specific model in your application.
-* How to make model-centric forms for creation and editing of specific database records.
+* How to make model-centric forms for creating and editing specific database records.
* How to generate select boxes from multiple types of data.
-* The date and time helpers Rails provides.
+* What date and time helpers Rails provides.
* What makes a file upload form different.
-* Some cases of building forms to external resources.
+* How to post forms to external resources and specify setting an `authenticity_token`.
* How to build complex forms.
--------------------------------------------------------------------------------
@@ -146,7 +146,7 @@ Output:
<label for="age_adult">I'm over 21</label>
```
-As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and `params[:age]` will contain either "child" or "adult".
+As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (`age`), the user will only be able to select one of them, and `params[:age]` will contain either "child" or "adult".
NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and,
by expanding the clickable region,
@@ -442,7 +442,12 @@ WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is
You can add arbitrary attributes to the options using hashes:
```html+erb
-<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %>
+<%= options_for_select(
+ [
+ ['Lisbon', 1, { 'data-size' => '2.8 million' }],
+ ['Madrid', 2, { 'data-size' => '3.2 million' }]
+ ], 2
+) %>
output:
@@ -474,6 +479,16 @@ As with other helpers, if you were to use the `select` helper on a form builder
<%= f.select(:city_id, ...) %>
```
+You can also pass a block to `select` helper:
+
+```erb
+<%= f.select(:city_id) do %>
+ <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%>
+ <%= content_tag(:option, c.first, value: c.last) %>
+ <% end %>
+<% end %>
+```
+
WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.
### Option Tags from a Collection of Arbitrary Objects
@@ -809,21 +824,21 @@ As a shortcut you can append [] to the name and omit the `:index` option. This i
produces exactly the same output as the previous example.
-Forms to external resources
+Forms to External Resources
---------------------------
-If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an `authenticity_token` for this resource. You can do it by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options:
+Rails' form helpers can also be used to build a form for posting data to an external resource. However, at times it can be necessary to set an `authenticity_token` for the resource; this can be done by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options:
```erb
-<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %>
+<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token' do %>
Form contents
<% end %>
```
-Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an `authenticity_token` hidden field at all. For doing this just pass `false` to the `:authenticity_token` option:
+Sometimes when submitting data to an external resource, like a payment gateway, the fields that can be used in the form are limited by an external API and it may be undesirable to generate an `authenticity_token`. To not send a token, simply pass `false` to the `:authenticity_token` option:
```erb
-<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %>
+<%= form_tag 'http://farfar.away/form', authenticity_token: false do %>
Form contents
<% end %>
```
@@ -998,4 +1013,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create
### Adding Fields on the Fly
-Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
+Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index dbcedba800..fa964e4450 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -21,19 +21,22 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer
-* The [RubyGems](http://rubygems.org) packaging system
- * To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org)
-* A working installation of the [SQLite3 Database](http://www.sqlite.org)
+* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer.
+* The [RubyGems](http://rubygems.org) packaging system, which is installed with Ruby
+ versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org).
+* A working installation of the [SQLite3 Database](http://www.sqlite.org).
Rails is a web application framework running on the Ruby programming language.
If you have no prior experience with Ruby, you will find a very steep learning
-curve diving straight into Rails. There are some good free resources on the
-Internet for learning Ruby, including:
+curve diving straight into Rails. There are several curated lists of online resources
+for learning Ruby:
-* [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com)
-* [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)
-* [Why's (Poignant) Guide to Ruby](http://mislav.uniqpath.com/poignant-guide/)
+* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
+* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby)
+
+Be aware that some resources, while still excellent, cover versions of Ruby as old as
+1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
+development with Rails.
What is Rails?
--------------
@@ -54,11 +57,13 @@ learned elsewhere, you may have a less happy experience.
The Rails philosophy includes two major guiding principles:
-* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and
- over again is a bad thing.
-* Convention Over Configuration - means that Rails makes assumptions about what
- you want to do and how you're going to do it, rather than requiring you to
- specify every little thing through endless configuration files.
+* **Don't Repeat Yourself:** DRY is a principle of software development which
+ states that "Every piece of knowledge must have a single, unambiguous, authoritative
+ representation within a system." By not writing the same information over and over
+ again, our code is more maintainable, more extensible, and less buggy.
+* **Convention Over Configuration:** Rails has opinions about the best way to do many
+ things in a web application, and defaults to this set of conventions, rather than
+ require that you specify every minutiae through endless configuration files.
Creating a New Rails Project
----------------------------
@@ -73,9 +78,9 @@ By following along with this guide, you'll create a Rails project called
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
-TIP: The examples below use `#` and `$` to denote superuser and regular
-user terminal prompts respectively in a UNIX-like OS. If you are using
-Windows, your prompt will look something like `c:\source_code>`
+TIP: The examples below use `$` to represent your terminal prompt in a UNIX-like OS,
+though it may have been customized to appear differently. If you are using Windows,
+your prompt will look something like `c:\source_code>`
### Installing Rails
@@ -84,21 +89,35 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
dollar sign `$` should be run in the command line. Verify that you have a
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 [Rails One Click](http://railsoneclick.com).
+
```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](http://www.sqlite.org).
+Verify that it is correctly installed and in your PATH:
+
+```bash
+$ sqlite3 --version
+```
+
+The program should report its version.
+
To install Rails, use the `gem install` command provided by RubyGems:
```bash
$ gem install rails
```
-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 [Rails One Click](http://railsoneclick.com).
-
To verify that you have everything installed correctly, you should be able to
run the following:
@@ -143,20 +162,20 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
-|app|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
+|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
|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.|
+|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://gembundler.com).|
-|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.|
+|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).|
-|vendor|A place for all third-party code. In a typical Rails application this includes vendored gems.|
+|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).|
+|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
Hello, Rails!
-------------
@@ -187,7 +206,7 @@ This will fire up WEBrick, a web server distributed with Ruby by default. To see
your application in action, open a browser window and navigate to
<http://localhost:3000>. You should see the Rails default information page:
-![Welcome aboard screenshot](images/getting_started/rails_welcome.jpg)
+![Welcome aboard screenshot](images/getting_started/rails_welcome.png)
TIP: To stop the web server, hit Ctrl+C in the terminal window where it's
running. To verify the server has stopped you should see your command prompt
@@ -315,18 +334,19 @@ Now that you've seen how to create a controller, an action and a view, let's
create something with a bit more substance.
In the Blog application, you will now create a new _resource_. A resource is the
-term used for a collection of similar objects, such as posts, people or animals.
+term used for a collection of similar objects, such as articles, people or
+animals.
You can create, read, update and destroy items for a resource and these
operations are referred to as _CRUD_ operations.
Rails provides a `resources` method which can be used to declare a standard REST
-resource. Here's what `config/routes.rb` should look like after the _post resource_
-is declared.
+resource. Here's what `config/routes.rb` should look like after the
+_article resource_ is declared.
```ruby
-Blog::Application.routes.draw do
+Rails.application.routes.draw do
- resources :posts
+ resources :articles
root 'welcome#index'
end
@@ -335,88 +355,95 @@ end
If you run `rake routes`, you'll see that it has defined routes for all the
standard RESTful actions. The meaning of the prefix column (and other columns)
will be seen later, but for now notice that Rails has inferred the
-singular form `post` and makes meaningful use of the distinction.
+singular form `article` and makes meaningful use of the distinction.
```bash
$ rake routes
- Prefix Verb URI Pattern Controller#Action
- posts GET /posts(.:format) posts#index
- POST /posts(.:format) posts#create
- new_post GET /posts/new(.:format) posts#new
-edit_post GET /posts/:id/edit(.:format) posts#edit
- post GET /posts/:id(.:format) posts#show
- PATCH /posts/:id(.:format) posts#update
- PUT /posts/:id(.:format) posts#update
- DELETE /posts/:id(.:format) posts#destroy
- root / welcome#index
-```
-
-In the next section, you will add the ability to create new posts in your
+ Prefix Verb URI Pattern Controller#Action
+ articles GET /articles(.:format) articles#index
+ POST /articles(.:format) articles#create
+ new_article GET /articles/new(.:format) articles#new
+edit_article GET /articles/:id/edit(.:format) articles#edit
+ article GET /articles/:id(.:format) articles#show
+ PATCH /articles/:id(.:format) articles#update
+ PUT /articles/:id(.:format) articles#update
+ DELETE /articles/:id(.:format) articles#destroy
+ root GET / welcome#index
+```
+
+In the next section, you will add the ability to create new articles in your
application and be able to view them. This is the "C" and the "R" from CRUD:
creation and reading. The form for doing this will look like this:
-![The new post form](images/getting_started/new_post.png)
+![The new article form](images/getting_started/new_article.png)
It will look a little basic for now, but that's ok. We'll look at improving the
styling for it afterwards.
### Laying down the ground work
-The first thing that you are going to need to create a new post within the
-application is a place to do that. A great place for that would be at `/posts/new`.
-With the route already defined, requests can now be made to `/posts/new` in the
-application. Navigate to <http://localhost:3000/posts/new> and you'll see a
-routing error:
+Firstly, you need a place within the application to create a new article. A
+great place for that would be at `/articles/new`. With the route already
+defined, requests can now be made to `/articles/new` in the application.
+Navigate to <http://localhost:3000/articles/new> and you'll see a routing
+error:
-![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png)
+![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png)
This error occurs because the route needs to have a controller defined in order
to serve the request. The solution to this particular problem is simple: create
-a controller called `PostsController`. You can do this by running this command:
+a controller called `ArticlesController`. You can do this by running this
+command:
```bash
-$ rails g controller posts
+$ rails g controller articles
```
-If you open up the newly generated `app/controllers/posts_controller.rb` you'll
-see a fairly empty controller:
+If you open up the newly generated `app/controllers/articles_controller.rb`
+you'll see a fairly empty controller:
```ruby
-class PostsController < ApplicationController
+class ArticlesController < ApplicationController
end
```
-A controller is simply a class that is defined to inherit from `ApplicationController`.
+A controller is simply a class that is defined to inherit from
+`ApplicationController`.
It's inside this class that you'll define methods that will become the actions
-for this controller. These actions will perform CRUD operations on the posts
+for this controller. These actions will perform CRUD operations on the articles
within our system.
-NOTE: There are `public`, `private` and `protected` methods in Ruby,
+NOTE: There are `public`, `private` and `protected` methods in Ruby,
but only `public` methods can be actions for controllers.
For more details check out [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/).
-If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:
+If you refresh <http://localhost:3000/articles/new> now, you'll get a new error:
-![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.png)
+![Unknown action new for ArticlesController!](images/getting_started/unknown_action_new_for_articles.png)
-This error indicates that Rails cannot find the `new` action inside the `PostsController`
-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.
+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.
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/posts_controller.rb`
-and inside the `PostsController` class, define a `new` method like this:
+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:
```ruby
-def new
+class ArticlesController < ApplicationController
+
+ def new
+ end
+
end
```
-With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new>
-you'll see another error:
+With the `new` method defined in `ArticlesController`, if you refresh
+<http://localhost:3000/articles/new> you'll see another error:
-![Template is missing for posts/new](images/getting_started/template_is_missing_posts_new.png)
+![Template is missing for articles/new](images/getting_started/template_is_missing_articles_new.png)
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
@@ -426,16 +453,16 @@ In the above image, the bottom line has been truncated. Let's see what the full
thing looks like:
<blockquote>
-Missing template posts/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
+Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
</blockquote>
That's quite a lot of text! Let's quickly go through and understand what each
part of it does.
The first part identifies what template is missing. In this case, it's the
-`posts/new` template. Rails will first look for this template. If not found,
+`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 `PostsController` inherits from `ApplicationController`.
+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,
@@ -451,34 +478,36 @@ 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/posts/new.html.erb`. The extension of this file name is key: the
+`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
-`posts/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 `posts/new.html.erb` and needs to
-be located inside the `app/views` directory of the application.
+`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.
-Go ahead now and create a new file at `app/views/posts/new.html.erb` and write
-this content in it:
+Go ahead now and create a new file at `app/views/articles/new.html.erb` and
+write this content in it:
```html
-<h1>New Post</h1>
+<h1>New Article</h1>
```
-When you refresh <http://localhost:3000/posts/new> you'll now see that the page
-has a title. The route, controller, action and view are now working
-harmoniously! It's time to create the form for a new post.
+When you refresh <http://localhost:3000/articles/new> you'll now see that the
+page has a title. The route, controller, action and view are now working
+harmoniously! It's time to create the form for a new article.
### The first form
To create a form within this template, you will use a <em>form
builder</em>. The primary form builder for Rails is provided by a helper
-method called `form_for`. To use this method, add this code into `app/views/posts/new.html.erb`:
+method called `form_for`. To use this method, add this code into
+`app/views/articles/new.html.erb`:
```html+erb
-<%= form_for :post do |f| %>
+<%= form_for :article do |f| %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -492,6 +521,7 @@ method called `form_for`. To use this method, add this code into `app/views/post
<p>
<%= f.submit %>
</p>
+
<% end %>
```
@@ -499,82 +529,86 @@ If you refresh the page now, you'll see the exact same form as in the example.
Building forms in Rails is really just that easy!
When you call `form_for`, you pass it an identifying object for this
-form. In this case, it's the symbol `:post`. This tells the `form_for`
+form. In this case, it's the symbol `:article`. This tells the `form_for`
helper what this form is for. Inside the block for this method, the
`FormBuilder` object - represented by `f` - is used to build two labels and two
-text fields, one each for the title and text of a post. Finally, a call to
+text fields, one each for the title and text of an article. Finally, a call to
`submit` on the `f` object will create a submit button for the form.
There's one problem with this form though. If you inspect the HTML that is
generated, by viewing the source of the page, you will see that the `action`
-attribute for the form is pointing at `/posts/new`. This is a problem because
+attribute for the form is pointing at `/articles/new`. This is a problem because
this route goes to the very page that you're on right at the moment, and that
-route should only be used to display the form for a new post.
+route should only be used to display the form for a new article.
The form needs to use a different URL in order to go somewhere else.
This can be done quite simply with the `:url` option of `form_for`.
Typically in Rails, the action that is used for new form submissions
like this is called "create", and so the form should be pointed to that action.
-Edit the `form_for` line inside `app/views/posts/new.html.erb` to look like this:
+Edit the `form_for` line inside `app/views/articles/new.html.erb` to look like
+this:
```html+erb
-<%= form_for :post, url: posts_path do |f| %>
+<%= form_for :article, url: articles_path do |f| %>
```
-In this example, the `posts_path` helper is passed to the `:url` option.
+In this example, the `articles_path` helper is passed to the `:url` option.
To see what Rails will do with this, we look back at the output of
`rake routes`:
```bash
$ rake routes
- Prefix Verb URI Pattern Controller#Action
- posts GET /posts(.:format) posts#index
- POST /posts(.:format) posts#create
- new_post GET /posts/new(.:format) posts#new
-edit_post GET /posts/:id/edit(.:format) posts#edit
- post GET /posts/:id(.:format) posts#show
- PATCH /posts/:id(.:format) posts#update
- PUT /posts/:id(.:format) posts#update
- DELETE /posts/:id(.:format) posts#destroy
- root / welcome#index
-```
-
-The `posts_path` helper tells Rails to point the form
-to the URI Pattern associated with the `posts` prefix; and
+ Prefix Verb URI Pattern Controller#Action
+ articles GET /articles(.:format) articles#index
+ POST /articles(.:format) articles#create
+ new_article GET /articles/new(.:format) articles#new
+edit_article GET /articles/:id/edit(.:format) articles#edit
+ article GET /articles/:id(.:format) articles#show
+ PATCH /articles/:id(.:format) articles#update
+ PUT /articles/:id(.:format) articles#update
+ DELETE /articles/:id(.:format) articles#destroy
+ root GET / welcome#index
+```
+
+The `articles_path` helper tells Rails to point the form
+to the URI Pattern associated with the `articles` prefix; and
the form will (by default) send a `POST` request
to that route. This is associated with the
-`create` action of the current controller, the `PostsController`.
+`create` action of the current controller, the `ArticlesController`.
With the form and its associated route defined, you will be able to fill in the
form and then click the submit button to begin the process of creating a new
-post, so go ahead and do that. When you submit the form, you should see a
+article, so go ahead and do that. When you submit the form, you should see a
familiar error:
-![Unknown action create for PostsController](images/getting_started/unknown_action_create_for_posts.png)
+![Unknown action create for ArticlesController]
+(images/getting_started/unknown_action_create_for_articles.png)
-You now need to create the `create` action within the `PostsController` for this
-to work.
+You now need to create the `create` action within the `ArticlesController` for
+this to work.
-### Creating posts
+### Creating articles
To make the "Unknown action" go away, you can define a `create` action within
-the `PostsController` class in `app/controllers/posts_controller.rb`, underneath
-the `new` action:
+the `ArticlesController` class in `app/controllers/articles_controller.rb`,
+underneath the `new` action, as shown:
```ruby
-class PostsController < ApplicationController
+class ArticlesController < ApplicationController
+
def new
end
def create
end
+
end
```
If you re-submit the form now, you'll see another familiar error: a template is
missing. That's ok, we can ignore that for now. What the `create` action should
-be doing is saving our new post to a database.
+be doing is saving our new article to the database.
When a form is submitted, the fields of the form are sent to Rails as
_parameters_. These parameters can then be referenced inside the controller
@@ -583,12 +617,12 @@ look like, change the `create` action to this:
```ruby
def create
- render text: params[:post].inspect
+ render plain: params[:article].inspect
end
```
-The `render` method here is taking a very simple hash with a key of `text` and
-value of `params[:post].inspect`. The `params` method is the object which
+The `render` method here is taking a very simple hash with a key of `plain` and
+value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
allows you to access the keys of the hash using either strings or symbols. In
@@ -598,14 +632,14 @@ If you re-submit the form one more time you'll now no longer get the missing
template error. Instead, you'll see something that looks like the following:
```ruby
-{"title"=>"First post!", "text"=>"This is my first post."}
+{"title"=>"First article!", "text"=>"This is my first article."}
```
-This action is now displaying the parameters for the post that are coming in
+This action is now displaying the parameters for the article that are coming in
from the form. However, this isn't really all that helpful. Yes, you can see the
parameters but nothing in particular is being done with them.
-### Creating the Post model
+### Creating the Article model
Models in Rails use a singular name, and their corresponding database tables use
a plural name. Rails provides a generator for creating models, which
@@ -613,17 +647,17 @@ most Rails developers tend to use when creating new models.
To create the new model, run this command in your terminal:
```bash
-$ rails generate model Post title:string text:text
+$ rails generate model Article title:string text:text
```
-With that command we told Rails that we want a `Post` model, together
+With that command we told Rails that we want a `Article` model, together
with a _title_ attribute of type string, and a _text_ attribute
-of type text. Those attributes are automatically added to the `posts`
-table in the database and mapped to the `Post` model.
+of type text. Those attributes are automatically added to the `articles`
+table in the database and mapped to the `Article` model.
Rails responded by creating a bunch of files. For
-now, we're only interested in `app/models/post.rb` and
-`db/migrate/20120419084633_create_posts.rb` (your name could be a bit
+now, we're only interested in `app/models/article.rb` and
+`db/migrate/20140120191729_create_articles.rb` (your name could be a bit
different). The latter is responsible
for creating the database structure, which is what we'll look at next.
@@ -642,13 +676,13 @@ 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/20120419084633_create_posts.rb` file (remember,
+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:
```ruby
-class CreatePosts < ActiveRecord::Migration
+class CreateArticles < ActiveRecord::Migration
def change
- create_table :posts do |t|
+ create_table :articles do |t|
t.string :title
t.text :text
@@ -658,12 +692,12 @@ class CreatePosts < ActiveRecord::Migration
end
```
-The above migration creates a method named `change` which will be called when you
-run this migration. The action defined in this method is also reversible, which
-means Rails knows how to reverse the change made by this migration, in case you
-want to reverse it later. When you run this migration it will create a
-`posts` table with one string column and a text column. It also creates two
-timestamp fields to allow Rails to track post creation and update times.
+The above migration creates a method named `change` which will be called when
+you run this migration. The action defined in this method is also reversible,
+which means Rails knows how to reverse the change made by this migration,
+in case you want to reverse it later. When you run this migration it will create
+an `articles` table with one string column and a text column. It also creates
+two timestamp fields to allow Rails to track article creation and update times.
TIP: For more information about migrations, refer to [Rails Database
Migrations](migrations.html).
@@ -674,14 +708,14 @@ At this point, you can use a rake command to run the migration:
$ rake db:migrate
```
-Rails will execute this migration command and tell you it created the Posts
+Rails will execute this migration command and tell you it created the Articles
table.
```bash
-== CreatePosts: migrating ====================================================
--- create_table(:posts)
+== CreateArticles: migrating ==================================================
+-- create_table(:articles)
-> 0.0019s
-== CreatePosts: migrated (0.0020s) ===========================================
+== CreateArticles: migrated (0.0020s) =========================================
```
NOTE. Because you're working in the development environment by default, this
@@ -692,66 +726,85 @@ invoking the command: `rake db:migrate RAILS_ENV=production`.
### Saving data in the controller
-Back in `PostsController`, we need to change the `create` action
-to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb`
-and change the `create` action to look like this:
+Back in `ArticlesController`, we need to change the `create` action
+to use the new `Article` model to save the data in the database.
+Open `app/controllers/articles_controller.rb` and change the `create` action to
+look like this:
```ruby
def create
- @post = Post.new(params[:post])
+ @article = Article.new(params[:article])
- @post.save
- redirect_to @post
+ @article.save
+ redirect_to @article
end
```
Here's what's going on: every Rails model can be initialized with its
respective attributes, which are automatically mapped to the respective
database columns. In the first line we do just that
-(remember that `params[:post]` contains the attributes we're interested in).
-Then, `@post.save` is responsible for saving the model in the database.
+(remember that `params[:article]` contains the attributes we're interested in).
+Then, `@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: As we'll see later, `@post.save` returns a boolean indicating
-whether the model was saved or not.
+TIP: As we'll see later, `@article.save` returns a boolean indicating
+whether the article was saved or not.
If you now go to
-<http://localhost:3000/posts/new> you'll *almost* be able to create a post. Try
-it! You should get an error that looks like this:
+<http://localhost:3000/articles/new> you'll *almost* be able to create an
+article. Try it! You should get an error that looks like this:
-![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.png)
+![Forbidden attributes for new article](images/getting_started/forbidden_attributes_for_new_article.png)
Rails has several security features that help you write secure applications,
and you're running into one of them now. This one is called
-`strong_parameters`, which requires us to tell Rails exactly which parameters
-we want to accept in our controllers. In this case, we want to allow the
-`title` and `text` parameters, so change your `create` controller action to
-look like this:
+`[strong_parameters](http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters)`,
+which requires us to tell Rails exactly which parameters are allowed into
+our controller actions.
+
+Why do you have to bother? The ability to grab and automatically assign
+all controller parameters to your model in one shot makes the programmer's
+job easier, but this convenience also allows malicious use. What if a
+request to the server was crafted to look like a new article form submit
+but also included extra fields with values that violated your applications
+integrity? They would be 'mass assigned' into your model and then into the
+database along with the good stuff - potentially breaking your application
+or worse.
+
+We have to whitelist our controller parameters to prevent wrongful
+mass assignment. In this case, we want to both allow and require the
+`title` and `text` parameters for valid use of `create`. The syntax for
+this introduces `require` and `permit`. The change will involve one line
+in the `create` action:
+
+```ruby
+ @article = Article.new(params.require(:article).permit(:title, :text))
+```
+
+This is often factored out into its own method so it can be reused by
+multiple actions in the same controller, for example `create` and `update`.
+Above and beyond mass assignment issues, the method is often made
+`private` to make sure it can't be called outside its intended context.
+Here is the result:
```ruby
def create
- @post = Post.new(post_params)
+ @article = Article.new(article_params)
- @post.save
- redirect_to @post
+ @article.save
+ redirect_to @article
end
private
- def post_params
- params.require(:post).permit(:title, :text)
+ def article_params
+ params.require(:article).permit(:title, :text)
end
```
-See the `permit`? It allows us to accept both `title` and `text` in this
-action.
-
-TIP: Note that `def post_params` is private. This new approach prevents an
-attacker from setting the model's attributes by manipulating the hash passed to
-the model.
-For more information, refer to
-[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
+TIP: For more information, refer to the reference above and
+[this blog article about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
-### Showing Posts
+### Showing Articles
If you submit the form again now, Rails will complain about not finding
the `show` action. That's not very useful though, so let's add the
@@ -761,68 +814,99 @@ As we have seen in the output of `rake routes`, the route for `show` action is
as follows:
```
-post GET /posts/:id(.:format) posts#show
+article GET /articles/:id(.:format) articles#show
```
The special syntax `:id` tells rails that this route expects an `:id`
-parameter, which in our case will be the id of the post.
+parameter, which in our case will be the id of the article.
As we did before, we need to add the `show` action in
-`app/controllers/posts_controller.rb` and its respective view.
+`app/controllers/articles_controller.rb` and its respective view.
+
+NOTE: A frequent practice is to place the standard CRUD actions in each
+controller in the following order: `index`, `show`, `new`, `edit`, `create`,
+`update` and `destroy`. You may use any order you choose, but keep in mind that
+these are public methods; as mentioned earlier in this guide, they must be
+placed before any private or protected method in the controller in order to
+work.
+
+Given that, let's add the `show` action, as follows:
```ruby
-def show
- @post = Post.find(params[:id])
-end
+class ArticlesController < ApplicationController
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ end
+
+ # snipped for brevity
```
-A couple of things to note. We use `Post.find` to find the post we're
+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
-reference to the post object. We do this because Rails will pass all instance
+reference to the article object. We do this because Rails will pass all instance
variables to the view.
-Now, create a new file `app/views/posts/show.html.erb` with the following
+Now, create a new file `app/views/articles/show.html.erb` with the following
content:
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
```
-With this change, you should finally be able to create new posts.
-Visit <http://localhost:3000/posts/new> and give it a try!
+With this change, you should finally be able to create new articles.
+Visit <http://localhost:3000/articles/new> and give it a try!
-![Show action for posts](images/getting_started/show_action_for_posts.png)
+![Show action for articles](images/getting_started/show_action_for_articles.png)
-### Listing all posts
+### Listing all articles
-We still need a way to list all our posts, so let's do that.
+We still need a way to list all our articles, so let's do that.
The route for this as per output of `rake routes` is:
```
-posts GET /posts(.:format) posts#index
+articles GET /articles(.:format) articles#index
```
-Add the corresponding `index` action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file:
+Add the corresponding `index` action for that route inside the
+`ArticlesController` in the `app/controllers/articles_controller.rb` file.
+When we write an `index` action, the usual practice is to place it as the
+first method in the controller. Let's do it:
```ruby
-def index
- @posts = Post.all
-end
+class ArticlesController < ApplicationController
+
+ def index
+ @articles = Article.all
+ end
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ end
+
+ # snipped for brevity
```
-And then finally, add view for this action, located at `app/views/posts/index.html.erb`:
+And then finally, add the view for this action, located at
+`app/views/articles/index.html.erb`:
```html+erb
-<h1>Listing posts</h1>
+<h1>Listing articles</h1>
<table>
<tr>
@@ -830,70 +914,71 @@ And then finally, add view for this action, located at `app/views/posts/index.ht
<th>Text</th>
</tr>
- <% @posts.each do |post| %>
+ <% @articles.each do |article| %>
<tr>
- <td><%= post.title %></td>
- <td><%= post.text %></td>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
</tr>
<% end %>
</table>
```
-Now if you go to `http://localhost:3000/posts` you will see a list of all the
-posts that you have created.
+Now if you go to `http://localhost:3000/articles` you will see a list of all the
+articles that you have created.
### Adding links
-You can now create, show, and list posts. Now let's add some links to
+You can now create, show, and list articles. Now let's add some links to
navigate through pages.
Open `app/views/welcome/index.html.erb` and modify it as follows:
```html+erb
<h1>Hello, Rails!</h1>
-<%= link_to 'My Blog', controller: 'posts' %>
+<%= link_to 'My Blog', controller: 'articles' %>
```
The `link_to` method is one of Rails' built-in view helpers. It creates a
hyperlink based on text to display and where to go - in this case, to the path
-for posts.
+for articles.
-Let's add links to the other views as well, starting with adding this "New Post"
-link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag:
+Let's add links to the other views as well, starting with adding this
+"New Article" link to `app/views/articles/index.html.erb`, placing it above the
+`<table>` tag:
```erb
-<%= link_to 'New post', new_post_path %>
+<%= link_to 'New article', new_article_path %>
```
-This link will allow you to bring up the form that lets you create a new post.
-You should also add a link to this template - `app/views/posts/new.html.erb` -
-to go back to the `index` action. Do this by adding this underneath the form in
-this template:
+This link will allow you to bring up the form that lets you create a new article.
+
+Now, add another link in `app/views/articles/new.html.erb`, underneath the
+form, to go back to the `index` action:
```erb
-<%= form_for :post do |f| %>
+<%= form_for :article, url: articles_path do |f| %>
...
<% end %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
-Finally, add another link to the `app/views/posts/show.html.erb` template to go
-back to the `index` action as well, so that people who are viewing a single post
-can go back and view the whole list again:
+Finally, add a link to the `app/views/articles/show.html.erb` template to
+go back to the `index` action as well, so that people who are viewing a single
+article can go back and view the whole list again:
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
TIP: If you want to link to an action in the same controller, you don't
@@ -906,92 +991,97 @@ and restart the web server when a change is made.
### Adding Some Validation
-The model file, `app/models/post.rb` is about as simple as it can get:
+The model file, `app/models/article.rb` is about as simple as it can get:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
end
```
-There isn't much to this file - but note that the `Post` class inherits from
+There isn't much to this file - but note that the `Article` class inherits from
`ActiveRecord::Base`. Active Record supplies a great deal of functionality to
your Rails models for free, including basic database CRUD (Create, Read, Update,
Destroy) operations, data validation, as well as sophisticated search support
and the ability to relate multiple models to one another.
Rails includes methods to help you validate the data that you send to models.
-Open the `app/models/post.rb` file and edit it:
+Open the `app/models/article.rb` file and edit it:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
end
```
-These changes will ensure that all posts have a title that is at least five
+These changes will ensure that all articles have a title that is at least five
characters long. Rails can validate a variety of conditions in a model,
including the presence or uniqueness of columns, their format, and the
existence of associated objects. Validations are covered in detail in [Active
Record Validations](active_record_validations.html)
-With the validation now in place, when you call `@post.save` on an invalid
-post, it will return `false`. If you open `app/controllers/posts_controller.rb`
-again, you'll notice that we don't check the result of calling `@post.save`
-inside the `create` action. If `@post.save` fails in this situation, we need to
-show the form back to the user. To do this, change the `new` and `create`
-actions inside `app/controllers/posts_controller.rb` to these:
+With the validation now in place, when you call `@article.save` on an invalid
+article, it will return `false`. If you open
+`app/controllers/articles_controller.rb` again, you'll notice that we don't
+check the result of calling `@article.save` inside the `create` action.
+If `@article.save` fails in this situation, we need to show the form back to the
+user. To do this, change the `new` and `create` actions inside
+`app/controllers/articles_controller.rb` to these:
```ruby
def new
- @post = Post.new
+ @article = Article.new
end
def create
- @post = Post.new(post_params)
+ @article = Article.new(article_params)
- if @post.save
- redirect_to @post
+ if @article.save
+ redirect_to @article
else
render 'new'
end
end
private
- def post_params
- params.require(:post).permit(:title, :text)
+ def article_params
+ params.require(:article).permit(:title, :text)
end
```
-The `new` action is now creating a new instance variable called `@post`, and
+The `new` action is now creating a new instance variable called `@article`, and
you'll see why that is in just a few moments.
Notice that inside the `create` action we use `render` instead of `redirect_to`
-when `save` returns `false`. The `render` method is used so that the `@post`
+when `save` returns `false`. The `render` method is used so that the `@article`
object is passed back to the `new` template when it is rendered. This rendering
-is done within the same request as the form submission, whereas the `redirect_to`
-will tell the browser to issue another request.
+is done within the same request as the form submission, whereas the
+`redirect_to` will tell the browser to issue another request.
If you reload
-<http://localhost:3000/posts/new> and
-try to save a post without a title, Rails will send you back to the
+<http://localhost:3000/articles/new> and
+try to save an article without a title, Rails will send you back to the
form, but that's not very useful. You need to tell the user that
something went wrong. To do that, you'll modify
-`app/views/posts/new.html.erb` to check for error messages:
+`app/views/articles/new.html.erb` to check for error messages:
```html+erb
-<%= form_for :post, url: posts_path do |f| %>
- <% if @post.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+<%= form_for :article, url: articles_path do |f| %>
+
+ <% if @article.errors.any? %>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1005,64 +1095,85 @@ something went wrong. To do that, you'll modify
<p>
<%= f.submit %>
</p>
+
<% end %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
A few things are going on. We check if there are any errors with
-`@post.errors.any?`, and in that case we show a list of all
-errors with `@post.errors.full_messages`.
+`@article.errors.any?`, and in that case we show a list of all
+errors with `@article.errors.full_messages`.
`pluralize` is a rails helper that takes a number and a string as its
arguments. If the number is greater than one, the string will be automatically
pluralized.
-The reason why we added `@post = Post.new` in the `PostsController` is that
-otherwise `@post` would be `nil` in our view, and calling
-`@post.errors.any?` would throw an error.
+The reason why we added `@article = Article.new` in the `ArticlesController` is
+that otherwise `@article` would be `nil` in our view, and calling
+`@article.errors.any?` would throw an error.
TIP: Rails automatically wraps fields that contain an error with a div
with class `field_with_errors`. You can define a css rule to make them
standout.
-Now you'll get a nice error message when saving a post without title when you
-attempt to do just that on the new post form [(http://localhost:3000/posts/new)](http://localhost:3000/posts/new).
+Now you'll get a nice error message when saving an article without title when
+you attempt to do just that on the new article form
+[(http://localhost:3000/articles/new)](http://localhost:3000/articles/new).
![Form With Errors](images/getting_started/form_with_errors.png)
-### Updating Posts
+### Updating Articles
We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating
-posts.
+articles.
-The first step we'll take is adding an `edit` action to the `PostsController`.
+The first step we'll take is adding an `edit` action to the `ArticlesController`,
+generally between the `new` and `create` actions, as shown:
```ruby
+def new
+ @article = Article.new
+end
+
def edit
- @post = Post.find(params[:id])
+ @article = Article.find(params[:id])
+end
+
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
end
```
The view will contain a form similar to the one we used when creating
-new posts. Create a file called `app/views/posts/edit.html.erb` and make
+new articles. Create a file called `app/views/articles/edit.html.erb` and make
it look as follows:
```html+erb
-<h1>Editing post</h1>
-
-<%= form_for :post, url: post_path(@post), method: :patch do |f| %>
- <% if @post.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+<h1>Editing article</h1>
+
+<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
+
+ <% if @article.errors.any? %>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1076,9 +1187,10 @@ it look as follows:
<p>
<%= f.submit %>
</p>
+
<% end %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
This time we point the form to the `update` action, which is not defined yet
@@ -1088,42 +1200,60 @@ The `method: :patch` option tells Rails that we want this form to be submitted
via the `PATCH` HTTP method which is the HTTP method you're expected to use to
**update** resources according to the REST protocol.
-TIP: By default forms built with the _form_for_ helper are sent via `POST`.
+The first parameter of `form_for` can be an object, say, `@article` which would
+cause the helper to fill in the form with the fields of the object. Passing in a
+symbol (`:article`) with the same name as the instance variable (`@article`)
+also automagically leads to the same behavior. This is what is happening here.
+More details can be found in [form_for documentation]
+(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for).
-Next we need to create the `update` action in `app/controllers/posts_controller.rb`:
+Next, we need to create the `update` action in
+`app/controllers/articles_controller.rb`.
+Add it between the `create` action and the `private` method:
```ruby
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
+end
+
def update
- @post = Post.find(params[:id])
+ @article = Article.find(params[:id])
- if @post.update(post_params)
- redirect_to @post
+ if @article.update(article_params)
+ redirect_to @article
else
render 'edit'
end
end
private
- def post_params
- params.require(:post).permit(:title, :text)
+ def article_params
+ params.require(:article).permit(:title, :text)
end
```
The new method, `update`, is used when you want to update a record
that already exists, and it accepts a hash containing the attributes
that you want to update. As before, if there was an error updating the
-post we want to show the form back to the user.
+article we want to show the form back to the user.
-We reuse the `post_params` method that we defined earlier for the create action.
+We reuse the `article_params` method that we defined earlier for the create
+action.
TIP: You don't need to pass all attributes to `update`. For
-example, if you'd call `@post.update(title: 'A new title')`
+example, if you'd call `@article.update(title: 'A new title')`
Rails would only update the `title` attribute, leaving all other
attributes untouched.
Finally, we want to show a link to the `edit` action in the list of all the
-posts, so let's add that now to `app/views/posts/index.html.erb` to make it
-appear next to the "Show" link:
+articles, so let's add that now to `app/views/articles/index.html.erb` to make
+it appear next to the "Show" link:
```html+erb
<table>
@@ -1133,26 +1263,26 @@ appear next to the "Show" link:
<th colspan="2"></th>
</tr>
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.title %></td>
- <td><%= post.text %></td>
- <td><%= link_to 'Show', post_path(post) %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ </tr>
+ <% end %>
</table>
```
-And we'll also add one to the `app/views/posts/show.html.erb` template as well,
-so that there's also an "Edit" link on a post's page. Add this at the bottom of
-the template:
+And we'll also add one to the `app/views/articles/show.html.erb` template as
+well, so that there's also an "Edit" link on an article's page. Add this at the
+bottom of the template:
```html+erb
...
-<%= link_to 'Back', posts_path %>
-| <%= link_to 'Edit', edit_post_path(@post) %>
+<%= link_to 'Back', articles_path %> |
+<%= link_to 'Edit', edit_article_path(@article) %>
```
And here's how our app looks so far:
@@ -1161,30 +1291,34 @@ And here's how our app looks so far:
### Using partials to clean up duplication in views
-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 some duplication
-by using a view partial. By convention, partial files are prefixed by an
-underscore.
+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.
TIP: You can read more about partials in the
[Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
-Create a new file `app/views/posts/_form.html.erb` with the following
+Create a new file `app/views/articles/_form.html.erb` with the following
content:
```html+erb
-<%= form_for @post do |f| %>
- <% if @post.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+<%= form_for @article do |f| %>
+
+ <% if @article.errors.any? %>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1198,46 +1332,47 @@ content:
<p>
<%= f.submit %>
</p>
+
<% end %>
```
Everything except for the `form_for` declaration remained the same.
The reason we can use this shorter, simpler `form_for` declaration
-to stand in for either of the other forms is that `@post` is a *resource*
+to stand in for either of the other forms is that `@article` is a *resource*
corresponding to a full set of RESTful routes, and Rails is able to infer
which URI and method to use.
For more information about this use of `form_for`, see
[Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style).
-Now, let's update the `app/views/posts/new.html.erb` view to use this new
+Now, let's update the `app/views/articles/new.html.erb` view to use this new
partial, rewriting it completely:
```html+erb
-<h1>New post</h1>
+<h1>New article</h1>
<%= render 'form' %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
-Then do the same for the `app/views/posts/edit.html.erb` view:
+Then do the same for the `app/views/articles/edit.html.erb` view:
```html+erb
-<h1>Edit post</h1>
+<h1>Edit article</h1>
<%= render 'form' %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', articles_path %>
```
-### Deleting Posts
+### Deleting Articles
-We're now ready to cover the "D" part of CRUD, deleting posts from the
+We're now ready to cover the "D" part of CRUD, deleting articles from the
database. Following the REST convention, the route for
-deleting posts as per output of `rake routes` is:
+deleting articles as per output of `rake routes` is:
```ruby
-DELETE /posts/:id(.:format) posts#destroy
+DELETE /articles/:id(.:format) articles#destroy
```
The `delete` routing method should be used for routes that destroy
@@ -1245,19 +1380,77 @@ resources. If this was left as a typical `get` route, it could be possible for
people to craft malicious URLs like this:
```html
-<a href='http://example.com/posts/1/destroy'>look at this cat!</a>
+<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
```
-We use the `delete` method for destroying resources, and this route is mapped to
-the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't
-exist yet, but is provided below:
+We use the `delete` method for destroying resources, and this route is mapped
+to the `destroy` action inside `app/controllers/articles_controller.rb`, which
+doesn't exist yet. The `destroy` method is generally the last CRUD action in
+the controller, and like the other public CRUD actions, it must be placed
+before any `private` or `protected` methods. Let's add it:
```ruby
def destroy
- @post = Post.find(params[:id])
- @post.destroy
+ @article = Article.find(params[:id])
+ @article.destroy
- redirect_to posts_path
+ redirect_to articles_path
+end
+```
+
+The complete `ArticlesController` in the
+`app/controllers/articles_controller.rb` file should now look like this:
+
+```ruby
+class ArticlesController < ApplicationController
+
+ def index
+ @articles = Article.all
+ end
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ @article = Article.new
+ end
+
+ def edit
+ @article = Article.find(params[:id])
+ end
+
+ def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
+ end
+
+ def update
+ @article = Article.find(params[:id])
+
+ if @article.update(article_params)
+ redirect_to @article
+ else
+ render 'edit'
+ end
+ end
+
+ def destroy
+ @article = Article.find(params[:id])
+ @article.destroy
+
+ redirect_to articles_path
+ end
+
+ private
+ def article_params
+ params.require(:article).permit(:title, :text)
+ end
end
```
@@ -1266,12 +1459,12 @@ them from the database. Note that we don't need to add a view for this
action since we're redirecting to the `index` action.
Finally, add a 'Destroy' link to your `index` action template
-(`app/views/posts/index.html.erb`) to wrap everything
+(`app/views/articles/index.html.erb`) to wrap everything
together.
```html+erb
-<h1>Listing Posts</h1>
-<%= link_to 'New post', new_post_path %>
+<h1>Listing Articles</h1>
+<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
@@ -1279,16 +1472,17 @@ together.
<th colspan="3"></th>
</tr>
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.title %></td>
- <td><%= post.text %></td>
- <td><%= link_to 'Show', post_path(post) %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post_path(post),
- method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ <td><%= link_to 'Destroy', article_path(article),
+ method: :delete,
+ data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+ <% end %>
</table>
```
@@ -1304,34 +1498,33 @@ Without this file, the confirmation dialog box wouldn't appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
Congratulations, you can now create, show, list, update and destroy
-posts.
+articles.
-TIP: In general, Rails encourages the use of resources objects in place
-of declaring routes manually.
-For more information about routing, see
+TIP: In general, Rails encourages using resources objects instead of
+declaring routes manually. For more information about routing, see
[Rails Routing from the Outside In](routing.html).
Adding a Second Model
---------------------
It's time to add a second model to the application. The second model will handle
-comments on posts.
+comments on articles.
### Generating a Model
We're going to see the same generator that we used before when creating
-the `Post` model. This time we'll create a `Comment` model to hold
-reference of post comments. Run this command in your terminal:
+the `Article` model. This time we'll create a `Comment` model to hold
+reference of article comments. Run this command in your terminal:
```bash
-$ rails generate model Comment commenter:string body:text post:references
+$ rails generate model Comment commenter:string body:text article:references
```
This command will generate four files:
| File | Purpose |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
-| db/migrate/20100207235629_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) |
+| db/migrate/20140120201010_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) |
| app/models/comment.rb | The Comment model |
| test/models/comment_test.rb | Testing harness for the comments model |
| test/fixtures/comments.yml | Sample comments for use in testing |
@@ -1340,12 +1533,12 @@ First, take a look at `app/models/comment.rb`:
```ruby
class Comment < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
end
```
-This is very similar to the `Post` model that you saw earlier. The difference
-is the line `belongs_to :post`, which sets up an Active Record _association_.
+This is very similar to the `Article` model that you saw earlier. The difference
+is the line `belongs_to :article`, which sets up an Active Record _association_.
You'll learn a little about associations in the next section of this guide.
In addition to the model, Rails has also made a migration to create the
@@ -1357,7 +1550,9 @@ class CreateComments < ActiveRecord::Migration
create_table :comments do |t|
t.string :commenter
t.text :body
- t.references :post, index: true
+
+ # this line adds an integer column called `article_id`.
+ t.references :article, index: true
t.timestamps
end
@@ -1386,26 +1581,27 @@ run against the current database, so in this case you will just see:
### Associating Models
Active Record associations let you easily declare the relationship between two
-models. In the case of comments and posts, you could write out the relationships
-this way:
+models. In the case of comments and articles, you could write out the
+relationships this way:
-* Each comment belongs to one post.
-* One post can have many comments.
+* Each comment belongs to one article.
+* One article can have many comments.
In fact, this is very close to the syntax that Rails uses to declare this
association. You've already seen the line of code inside the `Comment` model
-(app/models/comment.rb) that makes each comment belong to a Post:
+(app/models/comment.rb) that makes each comment belong to an Article:
```ruby
class Comment < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
end
```
-You'll need to edit `app/models/post.rb` to add the other side of the association:
+You'll need to edit `app/models/article.rb` to add the other side of the
+association:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
@@ -1413,29 +1609,31 @@ end
```
These two declarations enable a good bit of automatic behavior. For example, if
-you have an instance variable `@post` containing a post, you can retrieve all
-the comments belonging to that post as an array using `@post.comments`.
+you have an instance variable `@article` containing an article, you can retrieve
+all the comments belonging to that article as an array using
+`@article.comments`.
TIP: For more information on Active Record associations, see the [Active Record
Associations](association_basics.html) guide.
### Adding a Route for Comments
-As with the `welcome` controller, we will need to add a route so that Rails knows
-where we would like to navigate to see `comments`. Open up the
+As with the `welcome` controller, we will need to add a route so that Rails
+knows where we would like to navigate to see `comments`. Open up the
`config/routes.rb` file again, and edit it as follows:
```ruby
-resources :posts do
+resources :articles do
resources :comments
end
```
-This creates `comments` as a _nested resource_ within `posts`. This is another
-part of capturing the hierarchical relationship that exists between posts and
-comments.
+This creates `comments` as a _nested resource_ within `articles`. This is
+another part of capturing the hierarchical relationship that exists between
+articles and comments.
-TIP: For more information on routing, see the [Rails Routing](routing.html) guide.
+TIP: For more information on routing, see the [Rails Routing](routing.html)
+guide.
### Generating a Controller
@@ -1459,27 +1657,27 @@ This creates six files and one empty directory:
| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller |
Like with any blog, our readers will create their comments directly after
-reading the post, and once they have added their comment, will be sent back to
-the post show page to see their comment now listed. Due to this, our
+reading the article, and once they have added their comment, will be sent back
+to the article show page to see their comment now listed. Due to this, our
`CommentsController` is there to provide a method to create comments and delete
spam comments when they arrive.
-So first, we'll wire up the Post show template
-(`app/views/posts/show.html.erb`) to let us make a new comment:
+So first, we'll wire up the Article show template
+(`app/views/articles/show.html.erb`) to let us make a new comment:
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
+<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
@@ -1493,22 +1691,22 @@ So first, we'll wire up the Post show template
</p>
<% end %>
-<%= link_to 'Back', posts_path %>
-| <%= link_to 'Edit', edit_post_path(@post) %>
+<%= link_to 'Back', articles_path %> |
+<%= link_to 'Edit', edit_article_path(@article) %>
```
-This adds a form on the `Post` show page that creates a new comment by
+This adds a form on the `Article` show page that creates a new comment by
calling the `CommentsController` `create` action. The `form_for` call here uses
-an array, which will build a nested route, such as `/posts/1/comments`.
+an array, which will build a nested route, such as `/articles/1/comments`.
Let's wire up the `create` in `app/controllers/comments_controller.rb`:
```ruby
class CommentsController < ApplicationController
def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(comment_params)
- redirect_to post_path(@post)
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.create(comment_params)
+ redirect_to article_path(@article)
end
private
@@ -1518,35 +1716,36 @@ class CommentsController < ApplicationController
end
```
-You'll see a bit more complexity here than you did in the controller for posts.
-That's a side-effect of the nesting that you've set up. Each request for a
-comment has to keep track of the post to which the comment is attached, thus the
-initial call to the `find` method of the `Post` model to get the post in question.
+You'll see a bit more complexity here than you did in the controller for
+articles. That's a side-effect of the nesting that you've set up. Each request
+for a comment has to keep track of the article to which the comment is attached,
+thus the initial call to the `find` method of the `Article` model to get the
+article in question.
In addition, the code takes advantage of some of the methods available for an
-association. We use the `create` method on `@post.comments` to create and save
-the comment. This will automatically link the comment so that it belongs to that
-particular post.
+association. We use the `create` method on `@article.comments` to create and
+save the comment. This will automatically link the comment so that it belongs to
+that particular article.
-Once we have made the new comment, we send the user back to the original post
-using the `post_path(@post)` helper. As we have already seen, this calls the
-`show` action of the `PostsController` which in turn renders the `show.html.erb`
-template. This is where we want the comment to show, so let's add that to the
-`app/views/posts/show.html.erb`.
+Once we have made the new comment, we send the user back to the original article
+using the `article_path(@article)` helper. As we have already seen, this calls
+the `show` action of the `ArticlesController` which in turn renders the
+`show.html.erb` template. This is where we want the comment to show, so let's
+add that to the `app/views/articles/show.html.erb`.
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
<h2>Comments</h2>
-<% @post.comments.each do |comment| %>
+<% @article.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
@@ -1559,7 +1758,7 @@ template. This is where we want the comment to show, so let's add that to the
<% end %>
<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
+<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
@@ -1573,26 +1772,26 @@ template. This is where we want the comment to show, so let's add that to the
</p>
<% end %>
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
+<%= link_to 'Edit Article', edit_article_path(@article) %> |
+<%= link_to 'Back to Articles', articles_path %>
```
-Now you can add posts and comments to your blog and have them show up in the
+Now you can add articles and comments to your blog and have them show up in the
right places.
-![Post with Comments](images/getting_started/post_with_comments.png)
+![Article with Comments](images/getting_started/article_with_comments.png)
Refactoring
-----------
-Now that we have posts and comments working, take a look at the
-`app/views/posts/show.html.erb` template. It is getting long and awkward. We can
-use partials to clean it up.
+Now that we have articles and comments working, take a look at the
+`app/views/articles/show.html.erb` template. It is getting long and awkward. We
+can use partials to clean it up.
### Rendering Partial Collections
-First, we will make a comment partial to extract showing all the comments for the
-post. Create the file `app/views/comments/_comment.html.erb` and put the
+First, we will make a comment partial to extract showing all the comments for
+the article. Create the file `app/views/comments/_comment.html.erb` and put the
following into it:
```html+erb
@@ -1607,25 +1806,25 @@ following into it:
</p>
```
-Then you can change `app/views/posts/show.html.erb` to look like the
+Then you can change `app/views/articles/show.html.erb` to look like the
following:
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
<h2>Comments</h2>
-<%= render @post.comments %>
+<%= render @article.comments %>
<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
+<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
@@ -1639,13 +1838,13 @@ following:
</p>
<% end %>
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
+<%= link_to 'Edit Article', edit_article_path(@article) %> |
+<%= link_to 'Back to Articles', articles_path %>
```
This will now render the partial in `app/views/comments/_comment.html.erb` once
-for each comment that is in the `@post.comments` collection. As the `render`
-method iterates over the `@post.comments` collection, it assigns each
+for each comment that is in the `@article.comments` collection. As the `render`
+method iterates over the `@article.comments` collection, it assigns each
comment to a local variable named the same as the partial, in this case
`comment` which is then available in the partial for us to show.
@@ -1655,7 +1854,7 @@ Let us also move that new comment section out to its own partial. Again, you
create a file `app/views/comments/_form.html.erb` containing:
```html+erb
-<%= form_for([@post, @post.comments.build]) do |f| %>
+<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
@@ -1670,27 +1869,27 @@ create a file `app/views/comments/_form.html.erb` containing:
<% end %>
```
-Then you make the `app/views/posts/show.html.erb` look like the following:
+Then you make the `app/views/articles/show.html.erb` look like the following:
```html+erb
<p>
<strong>Title:</strong>
- <%= @post.title %>
+ <%= @article.title %>
</p>
<p>
<strong>Text:</strong>
- <%= @post.text %>
+ <%= @article.text %>
</p>
<h2>Comments</h2>
-<%= render @post.comments %>
+<%= render @article.comments %>
<h2>Add a comment:</h2>
<%= render "comments/form" %>
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
+<%= link_to 'Edit Article', edit_article_path(@article) %> |
+<%= link_to 'Back to Articles', articles_path %>
```
The second render just defines the partial template we want to render,
@@ -1698,8 +1897,8 @@ The second render just defines the partial template we want to render,
string and realize that you want to render the `_form.html.erb` file in
the `app/views/comments` directory.
-The `@post` object is available to any partials rendered in the view because we
-defined it as an instance variable.
+The `@article` object is available to any partials rendered in the view because
+we defined it as an instance variable.
Deleting Comments
-----------------
@@ -1723,30 +1922,30 @@ So first, let's add the delete link in the
</p>
<p>
- <%= link_to 'Destroy Comment', [comment.post, comment],
+ <%= link_to 'Destroy Comment', [comment.article, comment],
method: :delete,
data: { confirm: 'Are you sure?' } %>
</p>
```
Clicking this new "Destroy Comment" link will fire off a `DELETE
-/posts/:post_id/comments/:id` to our `CommentsController`, which can then use
-this to find the comment we want to delete, so let's add a `destroy` action to our
-controller (`app/controllers/comments_controller.rb`):
+/articles/:article_id/comments/:id` to our `CommentsController`, which can then
+use this to find the comment we want to delete, so let's add a `destroy` action
+to our controller (`app/controllers/comments_controller.rb`):
```ruby
class CommentsController < ApplicationController
def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(comment_params)
- redirect_to post_path(@post)
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.create(comment_params)
+ redirect_to article_path(@article)
end
def destroy
- @post = Post.find(params[:post_id])
- @comment = @post.comments.find(params[:id])
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.find(params[:id])
@comment.destroy
- redirect_to post_path(@post)
+ redirect_to article_path(@article)
end
private
@@ -1756,20 +1955,20 @@ class CommentsController < ApplicationController
end
```
-The `destroy` action will find the post we are looking at, locate the comment
-within the `@post.comments` collection, and then remove it from the
-database and send us back to the show action for the post.
+The `destroy` action will find the article we are looking at, locate the comment
+within the `@article.comments` collection, and then remove it from the
+database and send us back to the show action for the article.
### Deleting Associated Objects
-If you delete a post then its associated comments will also need to be deleted.
-Otherwise they would simply occupy space in the database. Rails allows you to
-use the `dependent` option of an association to achieve this. Modify the Post
-model, `app/models/post.rb`, as follows:
+If you delete an article, its associated comments will also need to be
+deleted, otherwise they would simply occupy space in the database. Rails allows
+you to use the `dependent` option of an association to achieve this. Modify the
+Article model, `app/models/article.rb`, as follows:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
@@ -1781,34 +1980,35 @@ Security
### Basic Authentication
-If you were to publish your blog online, anybody would be able to add, edit and
-delete posts or delete comments.
+If you were to publish your blog online, anyone would be able to add, edit and
+delete articles or delete comments.
Rails provides a very simple HTTP authentication system that will work nicely in
this situation.
-In the `PostsController` we need to have a way to block access to the various
-actions if the person is not authenticated, here we can use the Rails
-`http_basic_authenticate_with` method, allowing access to the requested
+In the `ArticlesController` we need to have a way to block access to the
+various actions if the person is not authenticated. Here we can use the Rails
+`http_basic_authenticate_with` method, which allows access to the requested
action if that method allows it.
To use the authentication system, we specify it at the top of our
-`PostsController`, in this case, we want the user to be authenticated on every
-action, except for `index` and `show`, so we write that in `app/controllers/posts_controller.rb`:
+`ArticlesController` in `app/controllers/articles_controller.rb`. In our case,
+we want the user to be authenticated on every action except `index` and `show`,
+so we write that:
```ruby
-class PostsController < ApplicationController
+class ArticlesController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
def index
- @posts = Post.all
+ @articles = Article.all
end
# snipped for brevity
```
-We also only want to allow authenticated users to delete comments, so in the
+We also want to allow only authenticated users to delete comments, so in the
`CommentsController` (`app/controllers/comments_controller.rb`) we write:
```ruby
@@ -1817,21 +2017,22 @@ class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
- @post = Post.find(params[:post_id])
- ...
+ @article = Article.find(params[:article_id])
+ # ...
end
# snipped for brevity
```
-Now if you try to create a new post, you will be greeted with a basic HTTP
+Now if you try to create a new article, you will be greeted with a basic HTTP
Authentication challenge
![Basic HTTP Authentication Challenge](images/getting_started/challenge.png)
Other authentication methods are available for Rails applications. Two popular
-authentication add-ons for Rails are the [Devise](https://github.com/plataformatec/devise)
-rails engine and the [Authlogic](https://github.com/binarylogic/authlogic) gem,
+authentication add-ons for Rails are the
+[Devise](https://github.com/plataformatec/devise) rails engine and
+the [Authlogic](https://github.com/binarylogic/authlogic) gem,
along with a number of others.
@@ -1887,15 +2088,16 @@ cannot be automatically detected by Rails and corrected.
Two very common sources of data that are not UTF-8:
-* Your text editor: Most text editors (such as TextMate), default to saving files as
- UTF-8. If your text editor does not, this can result in special characters that you
- enter in your templates (such as é) to appear as a diamond with a question mark inside
- in the browser. This also applies to your i18n translation files.
- Most editors that do not already default to UTF-8 (such as some versions of
- Dreamweaver) offer a way to change the default to UTF-8. Do so.
-* Your database: Rails defaults to converting data from your database into UTF-8 at
- the boundary. However, if your database is not using UTF-8 internally, it may not
- be able to store all characters that your users enter. For instance, if your database
- is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese
- character, the data will be lost forever once it enters the database. If possible,
- use UTF-8 as the internal storage of your database.
+* Your text editor: Most text editors (such as TextMate), default to saving
+ files as UTF-8. If your text editor does not, this can result in special
+ characters that you enter in your templates (such as é) to appear as a diamond
+ with a question mark inside in the browser. This also applies to your i18n
+ translation files. Most editors that do not already default to UTF-8 (such as
+ some versions of Dreamweaver) offer a way to change the default to UTF-8. Do
+ so.
+* Your database: Rails defaults to converting data from your database into UTF-8
+ at the boundary. However, if your database is not using UTF-8 internally, it
+ may not be able to store all characters that your users enter. For instance,
+ if your database is using Latin-1 internally, and your user enters a Russian,
+ Hebrew, or Japanese character, the data will be lost forever once it enters
+ the database. If possible, use UTF-8 as the internal storage of your database.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 8dfb17a681..c1b575c7b7 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -92,7 +92,7 @@ Rails adds all `.rb` and `.yml` files from the `config/locales` directory to you
The default `en.yml` locale in this directory contains a sample pair of translation strings:
-```ruby
+```yaml
en:
hello: "Hello world"
```
@@ -137,7 +137,7 @@ If you want to translate your Rails application to a **single language other tha
However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
+WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this:
@@ -179,7 +179,7 @@ end
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
- I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
+ I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```
@@ -192,7 +192,7 @@ We can also set the locale from the _subdomain_ in a very similar way:
# in your /etc/hosts file to try this out locally
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
- I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
+ I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```
@@ -212,17 +212,16 @@ The most usual way of setting (and passing) the locale would be to include it in
This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though.
-Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course.
+Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course.
-Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method).
+Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method).
We can include something like this in our `ApplicationController` then:
```ruby
# app/controllers/application_controller.rb
-def default_url_options(options={})
- logger.debug "default_url_options is passed options: #{options.inspect}\n"
- { locale: I18n.locale }
+def default_url_options(options = {})
+ { locale: I18n.locale }.merge options
end
```
@@ -310,7 +309,7 @@ You most probably have something like this in one of your applications:
```ruby
# config/routes.rb
-Yourapp::Application.routes.draw do
+Rails.application.routes.draw do
root to: "home#index"
end
```
@@ -370,7 +369,7 @@ NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do
So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
-```ruby
+```yaml
# config/locales/en.yml
en:
hello_world: Hello world!
@@ -422,7 +421,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali
And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English):
-```ruby
+```yaml
# config/locales/pirate.yml
pirate:
time:
@@ -681,62 +680,13 @@ NOTE: Automatic conversion to HTML safe translate text is only available from th
![i18n demo html safe](images/i18n/demo_html_safe.png)
-How to Store your Custom Translations
--------------------------------------
-
-The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2]
-
-For example a Ruby Hash providing translations can look like this:
-
-```ruby
-{
- pt: {
- foo: {
- bar: "baz"
- }
- }
-}
-```
-
-The equivalent YAML file would look like this:
-
-```ruby
-pt:
- foo:
- bar: baz
-```
-
-As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz".
-
-Here is a "real" example from the Active Support `en.yml` translations YAML file:
-
-```ruby
-en:
- date:
- formats:
- default: "%Y-%m-%d"
- short: "%b %d"
- long: "%B %d, %Y"
-```
-
-So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`:
-
-```ruby
-I18n.t 'date.formats.short'
-I18n.t 'formats.short', scope: :date
-I18n.t :short, scope: 'date.formats'
-I18n.t :short, scope: [:date, :formats]
-```
-
-Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats.
-
### Translations for Active Record Models
You can use the methods `Model.model_name.human` and `Model.human_attribute_name(attribute)` to transparently look up translations for your model and attribute names.
For example when you add the following translations:
-```ruby
+```yaml
en:
activerecord:
models:
@@ -751,7 +701,7 @@ Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("
You can also set a plural form for model names, adding as following:
-```ruby
+```yaml
en:
activerecord:
models:
@@ -921,6 +871,55 @@ Rails uses fixed strings and other localizations, such as format strings and oth
* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope.
+How to Store your Custom Translations
+-------------------------------------
+
+The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2]
+
+For example a Ruby Hash providing translations can look like this:
+
+```yaml
+{
+ pt: {
+ foo: {
+ bar: "baz"
+ }
+ }
+}
+```
+
+The equivalent YAML file would look like this:
+
+```yaml
+pt:
+ foo:
+ bar: baz
+```
+
+As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz".
+
+Here is a "real" example from the Active Support `en.yml` translations YAML file:
+
+```yaml
+en:
+ date:
+ formats:
+ default: "%Y-%m-%d"
+ short: "%b %d"
+ long: "%B %d, %Y"
+```
+
+So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`:
+
+```ruby
+I18n.t 'date.formats.short'
+I18n.t 'formats.short', scope: :date
+I18n.t :short, scope: 'date.formats'
+I18n.t :short, scope: [:date, :formats]
+```
+
+Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats.
+
Customize your I18n Setup
-------------------------
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 5e2e0ad3e3..00b2761716 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -166,6 +166,7 @@ is called.
COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help)
def run_command!(command)
+ command = parse_command(command)
if COMMAND_WHITELIST.include?(command)
send(command)
else
@@ -178,8 +179,7 @@ With the `server` command, Rails will further run the following code:
```ruby
def set_application_directory!
- Dir.chdir(File.expand_path('../../', APP_PATH)) unless
- File.exist?(File.expand_path("config.ru"))
+ Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
def server
@@ -187,6 +187,8 @@ def server
require_command!("server")
Rails::Server.new.tap do |server|
+ # We need to require application after the server sets environment,
+ # otherwise the --environment option given to the server won't propagate.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
@@ -207,6 +209,7 @@ sets up the `Rails::Server` class.
require 'fileutils'
require 'optparse'
require 'action_dispatch'
+require 'rails'
module Rails
class Server < ::Rack::Server
@@ -270,10 +273,10 @@ def parse_options(args)
options = default_options
# Don't evaluate CGI ISINDEX parameters.
- # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
+ # http://www.meb.uni-bonn.de/docs/cgi/cl.html
args.clear if ENV.include?("REQUEST_METHOD")
- options.merge! opt_parser.parse! args
+ options.merge! opt_parser.parse!(args)
options[:config] = ::File.expand_path(options[:config])
ENV["RACK_ENV"] = options[:environment]
options
@@ -284,13 +287,16 @@ With the `default_options` set to this:
```ruby
def default_options
+ environment = ENV['RACK_ENV'] || 'development'
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
+
{
- environment: ENV['RACK_ENV'] || "development",
- pid: nil,
- Port: 9292,
- Host: "0.0.0.0",
- AccessLog: [],
- config: "config.ru"
+ :environment => environment,
+ :pid => nil,
+ :Port => 9292,
+ :Host => default_host,
+ :AccessLog => [],
+ :config => "config.ru"
}
end
```
@@ -348,6 +354,7 @@ private
def print_boot_information
...
puts "=> Run `rails server -h` for more startup options"
+ ...
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
@@ -434,7 +441,11 @@ The `app` method here is defined like so:
```ruby
def app
- @app ||= begin
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
+end
+...
+private
+ def build_app_and_options_from_config
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
@@ -443,7 +454,10 @@ def app
self.options.merge! options
app
end
-end
+
+ def build_app_from_string
+ Rack::Builder.new_from_string(self.options[:builder])
+ end
```
The `options[:config]` value defaults to `config.ru` which contains this:
@@ -459,8 +473,14 @@ run <%= app_const %>
The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code:
```ruby
-app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
- TOPLEVEL_BINDING, config
+app = new_from_string cfgfile, config
+
+...
+
+def self.new_from_string(builder_script, file="(rackup)")
+ eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
+ TOPLEVEL_BINDING, file, 0
+end
```
The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run:
@@ -473,11 +493,22 @@ require ::File.expand_path('../config/environment', __FILE__)
This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup.
-This file begins with requiring `config/application.rb`.
+This file begins with requiring `config/application.rb`:
+
+```ruby
+require File.expand_path('../application', __FILE__)
+```
### `config/application.rb`
-This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but **wouldn't** be the case with Passenger.
+This file requires `config/boot.rb`:
+
+```ruby
+require File.expand_path('../boot', __FILE__)
+```
+
+But only if it hasn't been required before, which would be the case in `rails server`
+but **wouldn't** be the case with Passenger.
Then the fun begins!
@@ -498,11 +529,12 @@ This file is responsible for requiring all the individual frameworks of Rails:
require "rails"
%w(
- active_record
- action_controller
- action_mailer
- rails/test_unit
- sprockets
+ active_record
+ action_controller
+ action_view
+ action_mailer
+ rails/test_unit
+ sprockets
).each do |framework|
begin
require "#{framework}/railtie"
@@ -522,11 +554,11 @@ I18n and Rails configuration are all being defined here.
### Back to `config/environment.rb`
The rest of `config/application.rb` defines the configuration for the
-`Rails::Application` which will be used once the application is fully
+`Rails::Application` which will be used once the application is fully
initialized. When `config/application.rb` has finished loading Rails and defined
the application namespace, we go back to `config/environment.rb`,
where the application is initialized. For example, if the application was called
-`Blog`, here we would find `Blog::Application.initialize!`, which is
+`Blog`, here we would find `Rails.application.initialize!`, which is
defined in `rails/application.rb`
### `railties/lib/rails/application.rb`
@@ -555,7 +587,7 @@ def run_initializers(group=:default, *args)
end
```
-The run_initializers code itself is tricky. What Rails is doing here is
+The `run_initializers` code itself is tricky. What Rails is doing here is
traversing all the class ancestors looking for those that respond to an
`initializers` method. It then sorts the ancestors by name, and runs them.
For example, the `Engine` class will make all the engines available by
@@ -568,7 +600,7 @@ initializers (like building the middleware stack) are run last. The `railtie`
initializers are the initializers which have been defined on the `Rails::Application`
itself and are run between the `bootstrap` and `finishers`.
-After this is done we go back to `Rack::Server`
+After this is done we go back to `Rack::Server`.
### Rack: lib/rack/server.rb
@@ -576,7 +608,11 @@ Last time we left when the `app` method was being defined:
```ruby
def app
- @app ||= begin
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
+end
+...
+private
+ def build_app_and_options_from_config
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
@@ -585,7 +621,10 @@ def app
self.options.merge! options
app
end
-end
+
+ def build_app_from_string
+ Rack::Builder.new_from_string(self.options[:builder])
+ end
```
At this point `app` is the Rails app itself (a middleware), and what
@@ -603,7 +642,7 @@ def build_app(app)
end
```
-Remember, `build_app` was called (by wrapped_app) in the last line of `Server#start`.
+Remember, `build_app` was called (by `wrapped_app`) in the last line of `Server#start`.
Here's how it looked like when we left:
```ruby
@@ -611,40 +650,50 @@ server.run wrapped_app, options, &blk
```
At this point, the implementation of `server.run` will depend on the
-server you're using. For example, if you were using Mongrel, here's what
+server you're using. For example, if you were using Puma, here's what
the `run` method would look like:
```ruby
-def self.run(app, options={})
- server = ::Mongrel::HttpServer.new(
- options[:Host] || '0.0.0.0',
- options[:Port] || 8080,
- options[:num_processors] || 950,
- options[:throttle] || 0,
- options[:timeout] || 60)
- # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
- # Use is similar to #run, replacing the app argument with a hash of
- # { path=>app, ... } or an instance of Rack::URLMap.
- if options[:map]
- if app.is_a? Hash
- app.each do |path, appl|
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- elsif app.is_a? URLMap
- app.instance_variable_get(:@mapping).each do |(host, path, appl)|
- next if !host.nil? && !options[:Host].nil? && options[:Host] != host
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- else
- raise ArgumentError, "first argument should be a Hash or URLMap"
- end
- else
- server.register('/', Rack::Handler::Mongrel.new(app))
+...
+DEFAULT_OPTIONS = {
+ :Host => '0.0.0.0',
+ :Port => 8080,
+ :Threads => '0:16',
+ :Verbose => false
+}
+
+def self.run(app, options = {})
+ options = DEFAULT_OPTIONS.merge(options)
+
+ if options[:Verbose]
+ app = Rack::CommonLogger.new(app, STDOUT)
end
+
+ if options[:environment]
+ ENV['RACK_ENV'] = options[:environment].to_s
+ end
+
+ server = ::Puma::Server.new(app)
+ min, max = options[:Threads].split(':', 2)
+
+ puts "Puma #{::Puma::Const::PUMA_VERSION} starting..."
+ puts "* Min threads: #{min}, max threads: #{max}"
+ puts "* Environment: #{ENV['RACK_ENV']}"
+ puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
+
+ server.add_tcp_listener options[:Host], options[:Port]
+ server.min_threads = min
+ server.max_threads = max
yield server if block_given?
- server.run.join
+
+ begin
+ server.run.join
+ rescue Interrupt
+ puts "* Gracefully stopping, waiting for requests to finish"
+ server.stop(true)
+ puts "* Goodbye!"
+ end
+
end
```
@@ -654,4 +703,4 @@ the last piece of our journey in the Rails initialization process.
This high level overview will help you understand when your code is
executed and how, and overall become a better Rails developer. If you
still want to know more, the Rails source code itself is probably the
-best place to go next.
+best place to go next. \ No newline at end of file
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 0513066f5a..1ac4e7f40c 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -4,7 +4,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c72b584ed6..bd33c5a146 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -236,15 +236,34 @@ render inline: "xml.p {'Horrid coding practice!'}", type: :builder
#### Rendering Text
-You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`:
+You can send plain text - with no markup at all - back to the browser by using
+the `:plain` option to `render`:
```ruby
-render text: "OK"
+render plain: "OK"
```
-TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML.
+TIP: Rendering pure text is most useful when you're responding to Ajax or web
+service requests that are expecting something other than proper HTML.
-NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `layout: true` option.
+NOTE: By default, if you use the `:plain` option, the text is rendered without
+using the current layout. If you want Rails to put the text into the current
+layout, you need to add the `layout: true` option.
+
+#### Rendering HTML
+
+You can send a HTML string back to the browser by using the `:html` option to
+`render`:
+
+```ruby
+render html: "<strong>Not Found</strong>".html_safe
+```
+
+TIP: This is useful when you're rendering a small snippet of HTML code.
+However, you might want to consider moving it to a template file if the markup
+is complex.
+
+NOTE: This option will escape HTML entities if the string is not html safe.
#### Rendering JSON
@@ -276,6 +295,22 @@ render js: "alert('Hello Rails');"
This will send the supplied string to the browser with a MIME type of `text/javascript`.
+#### Rendering raw body
+
+You can send a raw content back to the browser, without setting any content
+type, by using the `:body` option to `render`:
+
+```ruby
+render body: "raw"
+```
+
+TIP: This option should be used only if you don't care about the content type of
+the response. Using `:plain` or `:html` might be more appropriate in most of the
+time.
+
+NOTE: Unless overriden, your response returned from this render option will be
+`text/html`, as that is the default content type of Action Dispatch response.
+
#### Options for `render`
Calls to the `render` method generally accept four options:
@@ -1119,11 +1154,11 @@ With this change, you can access an instance of the `@products` collection as th
You can also pass in arbitrary local variables to any partial you are rendering with the `locals: {}` option:
```erb
-<%= render partial: "products", collection: @products,
+<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Products Page"} %>
```
-Would render a partial `_products.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`.
+In this case, the partial will have access to a local variable `title` with the value "Products Page".
TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option.
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 93729c6f72..8f119f36aa 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -20,7 +20,7 @@ Only the latest release series will receive bug fixes. When enough bugs are
fixed and its deemed worthy to release a new gem, this is the branch it happens
from.
-**Currently included series:** 4.0.z
+**Currently included series:** 4.1.z, 4.0.z
Security Issues
---------------
@@ -35,7 +35,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** 4.0.z, 3.2.z
+**Currently included series:** 4.1.z, 4.0.z
Severe Security Issues
----------------------
@@ -44,7 +44,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** 4.0.z, 3.2.z
+**Currently included series:** 4.1.z, 4.0.z, 3.2.z
Unsupported Release Series
--------------------------
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index 5d5c2724b1..c61ccfe94a 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -18,9 +18,10 @@ After reading this guide, you will know:
Migration Overview
------------------
-Migrations are a convenient way to alter your database schema over time in a
-consistent and easy way. They use a Ruby DSL so that you don't have to write
-SQL by hand, allowing your schema and changes to be database independent.
+Migrations are a convenient way to
+[alter your database schema over time](http://en.wikipedia.org/wiki/Schema_migration)
+in a consistent and easy way. They use a Ruby DSL so that you don't have to
+write SQL by hand, allowing your schema and changes to be database independent.
You can think of each migration as being a new 'version' of the database. A
schema starts off with nothing in it, and each migration modifies it to add or
@@ -494,6 +495,7 @@ class ExampleMigration < ActiveRecord::Migration
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
+end
```
Using `reversible` will ensure that the instructions are executed in the
@@ -642,7 +644,7 @@ method for all the migrations that have not yet been run. If there are
no such migrations, it exits. It will run these migrations in order based
on the date of the migration.
-Note that running the `db:migrate` also invokes the `db:schema:dump` task, which
+Note that running the `db:migrate` task also invokes the `db:schema:dump` task, which
will update your `db/schema.rb` file to match the structure of your database.
If you specify a target version, Active Record will run the required migrations
@@ -818,159 +820,6 @@ The `revert` method can be helpful when writing a new migration to undo
previous migrations in whole or in part
(see [Reverting Previous Migrations](#reverting-previous-migrations) above).
-Using Models in Your Migrations
--------------------------------
-
-When creating or updating data in a migration it is often tempting to use one
-of your models. After all, they exist to provide easy access to the underlying
-data. This can be done, but some caution should be observed.
-
-For example, problems occur when the model uses database columns which are (1)
-not currently in the database and (2) will be created by this or a subsequent
-migration.
-
-Consider this example, where Alice and Bob are working on the same code base
-which contains a `Product` model:
-
-Bob goes on vacation.
-
-Alice creates a migration for the `products` table which adds a new column and
-initializes it:
-
-```ruby
-# db/migrate/20100513121110_add_flag_to_product.rb
-
-class AddFlagToProduct < ActiveRecord::Migration
- def change
- add_column :products, :flag, :boolean
- reversible do |dir|
- dir.up { Product.update_all flag: false }
- end
- end
-end
-```
-
-She also adds a validation to the `Product` model for the new column:
-
-```ruby
-# app/models/product.rb
-
-class Product < ActiveRecord::Base
- validates :flag, inclusion: { in: [true, false] }
-end
-```
-
-Alice adds a second migration which adds another column to the `products`
-table and initializes it:
-
-```ruby
-# db/migrate/20100515121110_add_fuzz_to_product.rb
-
-class AddFuzzToProduct < ActiveRecord::Migration
- def change
- add_column :products, :fuzz, :string
- reversible do |dir|
- dir.up { Product.update_all fuzz: 'fuzzy' }
- end
- end
-end
-```
-
-She also adds a validation to the `Product` model for the new column:
-
-```ruby
-# app/models/product.rb
-
-class Product < ActiveRecord::Base
- validates :flag, inclusion: { in: [true, false] }
- validates :fuzz, presence: true
-end
-```
-
-Both migrations work for Alice.
-
-Bob comes back from vacation and:
-
-* Updates the source - which contains both migrations and the latest version
- of the Product model.
-* Runs outstanding migrations with `rake db:migrate`, which
- includes the one that updates the `Product` model.
-
-The migration crashes because when the model attempts to save, it tries to
-validate the second added column, which is not in the database when the _first_
-migration runs:
-
-```
-rake aborted!
-An error has occurred, this and all later migrations canceled:
-
-undefined method `fuzz' for #<Product:0x000001049b14a0>
-```
-
-A fix for this is to create a local model within the migration. This keeps
-Rails from running the validations, so that the migrations run to completion.
-
-When using a local model, it's a good idea to call
-`Product.reset_column_information` to refresh the Active Record cache for the
-`Product` model prior to updating data in the database.
-
-If Alice had done this instead, there would have been no problem:
-
-```ruby
-# db/migrate/20100513121110_add_flag_to_product.rb
-
-class AddFlagToProduct < ActiveRecord::Migration
- class Product < ActiveRecord::Base
- end
-
- def change
- add_column :products, :flag, :boolean
- Product.reset_column_information
- reversible do |dir|
- dir.up { Product.update_all flag: false }
- end
- end
-end
-```
-
-```ruby
-# db/migrate/20100515121110_add_fuzz_to_product.rb
-
-class AddFuzzToProduct < ActiveRecord::Migration
- class Product < ActiveRecord::Base
- end
-
- def change
- add_column :products, :fuzz, :string
- Product.reset_column_information
- reversible do |dir|
- dir.up { Product.update_all fuzz: 'fuzzy' }
- end
- end
-end
-```
-
-There are other ways in which the above example could have gone badly.
-
-For example, imagine that Alice creates a migration that selectively
-updates the `description` field on certain products. She runs the
-migration, commits the code, and then begins working on the next feature,
-which is to add a new column `fuzz` to the products table.
-
-She creates two migrations for this new feature, one which adds the new
-column, and a second which selectively updates the `fuzz` column based on
-other product attributes.
-
-These migrations run just fine, but when Bob comes back from his vacation
-and calls `rake db:migrate` to run all the outstanding migrations, he gets a
-subtle bug: The descriptions have defaults, and the `fuzz` column is present,
-but `fuzz` is `nil` on all products.
-
-The solution is again to use `Product.reset_column_information` before
-referencing the Product model in a migration, ensuring the Active Record's
-knowledge of the table structure is current before manipulating data in those
-records.
-
Schema Dumping and You
----------------------
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 855fab18e3..4f0634d955 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -17,9 +17,9 @@ Model setup
To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
-First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be build.
+First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built.
-If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded.
+If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded.
Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`.
@@ -220,6 +220,6 @@ As you can see it has generated 2 `project name` inputs, one for each new `proje
You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance.
-NOTE: The reason that `fields_for` constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
+NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep.
TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example.
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 720ca5d117..fe4215839f 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -92,12 +92,12 @@ Run `rake` to run the test. This test should fail because we haven't implemented
Great - now you are ready to start development.
-In `lib/yaffle.rb`, add `require "yaffle/core_ext"`:
+In `lib/yaffle.rb`, add `require 'yaffle/core_ext'`:
```ruby
# yaffle/lib/yaffle.rb
-require "yaffle/core_ext"
+require 'yaffle/core_ext'
module Yaffle
end
@@ -149,7 +149,7 @@ end
```ruby
# yaffle/lib/yaffle.rb
-require "yaffle/core_ext"
+require 'yaffle/core_ext'
require 'yaffle/acts_as_yaffle'
module Yaffle
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 9c92cf3aea..b1b4c8fa4e 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -27,10 +27,9 @@ Rails on Rack
### Rails Application's Rack Object
-`ApplicationName::Application` is the primary Rack application object of a Rails
+`Rails.application` is the primary Rack application object of a Rails
application. Any Rack compliant web server should be using
-`ApplicationName::Application` object to serve a Rails
-application. `Rails.application` refers to the same application object.
+`Rails.application` object to serve a Rails application.
### `rails server`
@@ -141,7 +140,7 @@ use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
-run MyApp::Application.routes
+run Rails.application.routes
```
The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
@@ -201,7 +200,7 @@ use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
use Rack::Runtime
...
-run Blog::Application.routes
+run Rails.application.routes
```
If you want to remove session related middleware, do the following:
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 70d4722068..0783bce442 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -352,15 +352,15 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | -------------------------------------- | ----------------- | ------------------- |
-| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments |
-| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments |
-| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment |
-| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment |
-| GET | /sekret/comments/:id(.:format) | comments#show | comment |
-| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment |
-| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------- | ----------------- | --------------------- |
+| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments_path |
+| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments_path |
+| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment_path |
+| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment_path |
+| GET | /sekret/comments/:id(.:format) | comments#show | comment_path |
+| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment_path |
+| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment_path |
The `:shallow_prefix` option adds the specified parameter to the named helpers:
@@ -374,15 +374,15 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | -------------------------------------- | ----------------- | ------------------- |
-| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments |
-| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments |
-| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment |
-| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment |
-| GET | /comments/:id(.:format) | comments#show | sekret_comment |
-| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment |
-| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------- | ----------------- | ------------------------ |
+| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments_path |
+| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments_path |
+| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment_path |
+| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment_path |
+| GET | /comments/:id(.:format) | comments#show | sekret_comment_path |
+| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment_path |
+| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment_path |
### Routing concerns
@@ -631,7 +631,7 @@ This will define a `user_path` method that will be available in controllers, hel
### HTTP Verb Constraints
-In general, you should use the `get`, `post`, `put` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once:
+In general, you should use the `get`, `post`, `put`, `patch` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once:
```ruby
match 'photos', to: 'photos#show', via: [:get, :post]
@@ -694,6 +694,8 @@ namespace :admin do
end
```
+NOTE: Request constraints work by calling a method on the <a href="action_controller_overview.html#the-request-object">Request object</a> with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String.
+
### Advanced Constraints
If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do:
@@ -709,7 +711,7 @@ class BlacklistConstraint
end
end
-TwitterClone::Application.routes.draw do
+Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: BlacklistConstraint.new
end
@@ -718,7 +720,7 @@ end
You can also specify constraints as a lambda:
```ruby
-TwitterClone::Application.routes.draw do
+Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) }
end
diff --git a/guides/source/security.md b/guides/source/security.md
index c367604d6f..0d347c9e4b 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -60,7 +60,7 @@ Many web applications have an authentication system: a user provides a user name
Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
-* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
+* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
```ruby
config.force_ssl = true
@@ -81,7 +81,7 @@ Here are some general guidelines on sessions.
* _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
-* _Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
+* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
### Session Storage
@@ -95,9 +95,16 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_.
-`config.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 `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`, e.g.:
+`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`, e.g.:
- YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
+ development:
+ secret_key_base: a75d...
+
+ test:
+ secret_key_base: 492f...
+
+ production:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
Older versions of Rails use CookieStore, which uses `secret_token` instead of `secret_key_base` that is used by EncryptedCookieStore. Read the upgrade documentation for more information.
@@ -144,7 +151,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec
reset_session
```
-If you use the popular RestfulAuthentication plugin for user management, add reset\_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_.
+If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_.
Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
@@ -232,24 +239,23 @@ Or the attacker places the code into the onmouseover event handler of an image:
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:
+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:
```ruby
-protect_from_forgery
+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, the session will be reset.
+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.
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
-def handle_unverified_request
- super
- sign_out_user # Example method that will destroy the user cookies.
+rescue_from ActionController::InvalidAuthenticityToken do |exception|
+ sign_out_user # Example method that will destroy the user cookies
end
```
-The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request.
+The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present or is incorrect on a non-GET request.
Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
@@ -307,7 +313,7 @@ def sanitize_filename(filename)
end
```
-A significant disadvantage of synchronous processing of file uploads (as the attachment\_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server.
+A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server.
The solution to this is best to _process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background.
@@ -549,7 +555,7 @@ Injection is very tricky, because the same code or parameter can be malicious in
### Whitelists versus Blacklists
-NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._
+NOTE: _When sanitizing, protecting or verifying something, prefer whitelists over blacklists._
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_:
@@ -915,6 +921,49 @@ Content-Type: text/html
Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. _In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._
+Unsafe Query Generation
+-----------------------
+
+Due to the way Active Record interprets parameters in combination with the way
+that Rack parses query parameters it was possible to issue unexpected database
+queries with `IS NULL` where clauses. As a response to that security issue
+([CVE-2012-2660](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/8SA-M3as7A8/Mr9fi9X4kNgJ),
+[CVE-2012-2694](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/jILZ34tAHF4/7x0hLH-o0-IJ)
+and [CVE-2013-0155](https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2012-2660/rubyonrails-security/c7jT-EeN9eI/L0u4e87zYGMJ))
+`deep_munge` method was introduced as a solution to keep Rails secure by default.
+
+Example of vulnerable code that could be used by attacker, if `deep_munge`
+wasn't performed is:
+
+```ruby
+unless params[:token].nil?
+ user = User.find_by_token(params[:token])
+ user.reset_password!
+end
+```
+
+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.
+
+To keep rails secure by default, `deep_munge` replaces some of the values with
+`nil`. Below table shows what the parameters look like based on `JSON` sent in
+request:
+
+| JSON | Parameters |
+|-----------------------------------|--------------------------|
+| `{ "person": null }` | `{ :person => nil }` |
+| `{ "person": [] }` | `{ :person => nil }` |
+| `{ "person": [null] }` | `{ :person => nil }` |
+| `{ "person": [null, null, ...] }` | `{ :person => nil }` |
+| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |
+
+It is possible to return to old behaviour and disable `deep_munge` configuring
+your application if you are aware of the risk and know how to handle it:
+
+```ruby
+config.action_dispatch.perform_deep_munge = false
+```
Default Headers
---------------
@@ -953,7 +1002,7 @@ _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS at
* X-Content-Type-Options
_'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file.
* X-Content-Security-Policy
-[A powerful mechanism for controlling which sites certain content types can be loaded from](http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html)
+[A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html)
* Access-Control-Allow-Origin
Used to control which sites are allowed to bypass same origin policies and send cross-origin requests.
* Strict-Transport-Security
@@ -962,7 +1011,7 @@ Used to control which sites are allowed to bypass same origin policies and send
Environmental Security
----------------------
-It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/initializers/secret_token.rb`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
+It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
Additional Resources
--------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 33cd3e868b..36d37f3af0 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -248,7 +248,7 @@ To see how a test failure is reported, you can add a failing test to the `post_t
```ruby
test "should not save post without title" do
post = Post.new
- assert !post.save
+ assert_not post.save
end
```
@@ -272,7 +272,7 @@ In the output, `F` denotes a failure. You can see the corresponding trace shown
```ruby
test "should not save post without title" do
post = Post.new
- assert !post.save, "Saved the post without a title"
+ assert_not post.save, "Saved the post without a title"
end
```
@@ -339,7 +339,7 @@ NOTE: The execution of each test method stops as soon as any error or an asserti
When a test fails you are presented with the corresponding backtrace. By default
Rails filters that backtrace and will only print lines relevant to your
-application. This eliminates the framwork noise and helps to focus on your
+application. This eliminates the framework noise and helps to focus on your
code. However there are situations when you want to see the full
backtrace. simply set the `BACKTRACE` environment variable to enable this
behavior:
@@ -401,8 +401,8 @@ Rails adds some custom assertions of its own to the `test/unit` framework:
| `assert_no_difference(expressions, message = nil, &amp;block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
-| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range|
-| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.|
+| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
+| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
| `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.|
You'll see the usage of some of these assertions in the next chapter.
@@ -518,8 +518,10 @@ You also have access to three instance variables in your functional tests:
### Setting Headers and CGI variables
-Headers and cgi variables can be set directly on the `@request`
-instance variable:
+[HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3)
+and
+[CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1)
+can be set directly on the `@request` instance variable:
```ruby
# setting a HTTP Header
@@ -794,7 +796,7 @@ when you initiate a Rails project.
Brief Note About `MiniTest`
-----------------------------
-Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
+Ruby ships with a vast Standard Library for all common use-cases including testing. Ruby 1.8 provided `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
us to use all of the basic assertions in our tests.
Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem.
@@ -937,12 +939,11 @@ Here's a unit test to test a mailer named `UserMailer` whose action `invite` is
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
- tests UserMailer
test "invite" do
# Send the email, then test that it got queued
email = UserMailer.create_invite('me@example.com',
'friend@example.com', Time.now).deliver
- assert !ActionMailer::Base.deliveries.empty?
+ assert_not ActionMailer::Base.deliveries.empty?
# Test the body of the sent email contains what we expect it to
assert_equal ['me@example.com'], email.from
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 72a4bb4c2d..da161f84c9 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -25,8 +25,6 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
-NOTE: This section is a work in progress.
-
### CSRF protection from remote `<script>` tags
Or, "whaaat my tests are failing!!!?"
@@ -62,7 +60,7 @@ If you want to use Spring as your application preloader you need to:
NOTE: User defined rake tasks will run in the `development` environment by
default. If you want them to run in other environments consult the
-[Spring README](https://github.com/jonleighton/spring#rake).
+[Spring README](https://github.com/rails/spring#rake).
### `config/secrets.yml`
@@ -79,12 +77,15 @@ secrets, you need to:
secret_key_base:
production:
- secret_key_base:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```
-2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to
- `secrets.yml` under the `production` section.
-
+2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to
+ set the SECRET_KEY_BASE environment variable for whichever users run the Rails
+ app in production mode. Alternately, you can simply copy the existing
+ `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml`
+ under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'.
+
3. Remove the `secret_token.rb` initializer.
4. Use `rake secret` to generate new keys for the `development` and `test` sections.
@@ -98,6 +99,63 @@ If your test helper contains a call to
is now done automatically when you `require 'test_help'`, although
leaving this line in your helper is not harmful in any way.
+### Cookies serializer
+
+Applications created before Rails 4.1 uses `Marshal` to serialize cookie values into
+the signed and encrypted cookie jars. If you want to use the new `JSON`-based format
+in your application, you can add an initializer file with the following content:
+
+```ruby
+Rails.application.config.action_dispatch.cookies_serializer = :hybrid
+```
+
+This would transparently migrate your existing `Marshal`-serialized cookies into the
+new `JSON`-based format.
+
+When using the `:json` or `:hybrid` serializer, you should beware that not all
+Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects
+will be serialized as strings, and `Hash`es will have their keys stringified.
+
+```ruby
+class CookiesController < ApplicationController
+ def set_cookie
+ cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
+ redirect_to action: 'read_cookie'
+ end
+
+ def read_cookie
+ cookies.encrypted[:expiration_date] # => "2014-03-20"
+ end
+end
+```
+
+It's advisable that you only store simple data (strings and numbers) in cookies.
+If you have to store complex objects, you would need to handle the conversion
+manually when reading the values on subsequent requests.
+
+If you use the cookie session store, this would apply to the `session` and
+`flash` hash as well.
+
+### Flash structure changes
+
+Flash message keys are
+[normalized to strings](https://github.com/rails/rails/commit/a668beffd64106a1e1fedb71cc25eaaa11baf0c1). They
+can still be accessed using either symbols or strings. Lopping through the flash
+will always yield string keys:
+
+```ruby
+flash["string"] = "a string"
+flash[:symbol] = "a symbol"
+
+# Rails < 4.1
+flash.keys # => ["string", :symbol]
+
+# Rails >= 4.1
+flash.keys # => ["string", "symbol"]
+```
+
+Make sure you are comparing Flash message keys against strings.
+
### Changes in JSON handling
There are a few major changes related to JSON handling in Rails 4.1.
@@ -130,7 +188,7 @@ Rails-specific features. For example:
```ruby
class FooBar
def as_json(options = nil)
- { foo: "bar" }
+ { foo: 'bar' }
end
end
@@ -148,7 +206,7 @@ part of the rewrite, the following features have been removed from the encoder:
2. Support for the `encode_json` hook
3. Option to encode `BigDecimal` objects as numbers instead of strings
-If you application depends on one of these features, you can get them back by
+If your application depends on one of these features, you can get them back by
adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder)
gem to your Gemfile.
@@ -249,6 +307,92 @@ authors = Author.where(name: 'Hank Moody').to_a
authors.compact!
```
+### Changes on Default Scopes
+
+Default scopes are no longer overriden by chained conditions.
+
+In previous versions when you defined a `default_scope` in a model
+it was overriden by chained conditions in the same field. Now it
+is merged like any other scope.
+
+Before:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
+After:
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
+
+User.where(state: 'inactive')
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
+```
+
+To get the previous behavior it is needed to explicitly remove the
+`default_scope` condition using `unscoped`, `unscope`, `rewhere` or
+`except`.
+
+```ruby
+class User < ActiveRecord::Base
+ default_scope { where state: 'pending' }
+ scope :active, -> { unscope(where: :state).where(state: 'active') }
+ scope :inactive, -> { rewhere state: 'inactive' }
+end
+
+User.all
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
+
+User.active
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
+
+User.inactive
+# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
+```
+
+### Rendering content from string
+
+Rails 4.1 introduces `:plain`, `:html`, and `:body` options to `render`. Those
+options are now the preferred way to render string-based content, as it allows
+you to specify which content type you want the response sent as.
+
+* `render :plain` will set the content type to `text/plain`
+* `render :html` will set the content type to `text/html`
+* `render :body` will *not* set the content type header.
+
+From the security standpoint, if you don't expect to have any markup in your
+response body, you should be using `render :plain` as most browsers will escape
+unsafe content in the response for you.
+
+We will be deprecating the use of `render :text` in a future version. So please
+start using the more precise `:plain:`, `:html`, and `:body` options instead.
+Using `render :text` may pose a security risk, as the content is sent as
+`text/html`.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -503,13 +647,13 @@ get 'こんにちは', controller: 'welcome', action: 'index'
```ruby
# Rails 3.x
- match "/" => "root#index"
+ match '/' => 'root#index'
# becomes
- match "/" => "root#index", via: :get
+ match '/' => 'root#index', via: :get
# or
- get "/" => "root#index"
+ get '/' => 'root#index'
```
* Rails 4.0 has removed `ActionDispatch::BestStandardsSupport` middleware, `<!DOCTYPE html>` already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers`.
@@ -554,9 +698,8 @@ Active Record Observer and Action Controller Sweeper have been extracted to the
### sprockets-rails
-* `assets:precompile:primary` has been removed. Use `assets:precompile` instead.
-* The `config.assets.compress` option should be changed to
-`config.assets.js_compressor` like so for instance:
+* `assets:precompile:primary` and `assets:precompile:all` have been removed. Use `assets:precompile` instead.
+* The `config.assets.compress` option should be changed to `config.assets.js_compressor` like so for instance:
```ruby
config.assets.js_compressor = :uglifier
@@ -564,14 +707,14 @@ config.assets.js_compressor = :uglifier
### sass-rails
-* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
+* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`.
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
-The following changes are meant for upgrading your application to Rails 3.2.16,
+The following changes are meant for upgrading your application to Rails 3.2.17,
the last 3.2.x version of Rails.
### Gemfile
@@ -579,7 +722,7 @@ the last 3.2.x version of Rails.
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.16'
+gem 'rails', '3.2.17'
group :assets do
gem 'sass-rails', '~> 3.2.6'
@@ -705,7 +848,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.static_cache_control = "public, max-age=3600"
+config.static_cache_control = 'public, max-age=3600'
```
### config/initializers/wrap_parameters.rb
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 3c04204c29..aba3c9ed61 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -111,7 +111,9 @@ paintIt = (element, backgroundColor, textColor) ->
element.style.color = textColor
$ ->
- $("a[data-background-color]").click ->
+ $("a[data-background-color]").click (e) ->
+ e.preventDefault()
+
backgroundColor = $(this).data("background-color")
textColor = $(this).data("text-color")
paintIt(this, backgroundColor, textColor)
@@ -180,7 +182,7 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:
$(document).ready ->
$("#new_post").on("ajax:success", (e, data, status, xhr) ->
$("#new_post").append xhr.responseText
- ).bind "ajax:error", (e, xhr, status, error) ->
+ ).on "ajax:error", (e, xhr, status, error) ->
$("#new_post").append "<p>ERROR</p>"
```
diff --git a/rails.gemspec b/rails.gemspec
index d1c199a97a..4800df0df4 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -27,5 +27,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', '~> 2.0.0'
+ s.add_dependency 'sprockets-rails', '~> 2.1'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e314ab5637..6a31a923a7 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,296 +1,61 @@
-* Write controller generated routes in routes.rb with single quotes.
+* Fix `console` and `generators` blocks defined at different environments.
- *Cristian Mircea Messel*
-
-* Only lookup `config.log_level` for stdlib `::Logger` instances.
- Assign it as is for third party loggers like `Log4r::Logger`.
-
- Fixes #13421.
-
- *Yves Senn*
-
-* The `Gemfile` of new applications depends on SDoc ~> 0.4.0.
-
- *Xavier Noria*
-
-* `test_help.rb` now automatically checks/maintains your test database
- schema. (Use `config.active_record.maintain_test_schema = false` to
- disable.)
-
- *Jon Leighton*
-
-* Configure `secrets.yml` and `database.yml` to read configuration
- from the system environment by default for production.
-
- *José Valim*
-
-* `config.assets.raise_runtime_errors` is set to true by default
-
- This option has been introduced in
- [sprockets-rails#100][https://github.com/rails/sprockets-rails/pull/100]
- and defaults to true in new applications in development.
-
- *Richard Schneeman*
-
-* Generates `html` and `text` templates for mailers by default.
-
- *Kassio Borges*
-
-* Move `secret_key_base` from `config/initializers/secret_token.rb`
- to `config/secrets.yml`.
-
- `secret_key_base` is now saved in `Rails.application.secrets.secret_key_base`
- and it fallbacks to the value of `config.secret_key_base` when it is not
- present in `config/secrets.yml`.
-
- `config/initializers/secret_token.rb` is not generated by default
- in new applications.
-
- *Guillermo Iguaran*
-
-* Generate a new `secrets.yml` file in the `config` folder for new
- applications. By default, this file contains the application's `secret_key_base`,
- but it could also be used to store other secrets such as access keys for external
- APIs.
-
- The secrets added to this file will be accessible via `Rails.application.secrets`.
- For example, with the following `secrets.yml`:
-
- development:
- secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
- some_api_key: SOMEKEY
-
- `Rails.application.secrets.some_api_key` will return `SOMEKEY` in the development
- environment.
-
- *Guillermo Iguaran*
-
-* Add `ENV['DATABASE_URL']` support in `rails dbconsole`. Fixes #13320.
-
- *Huiming Teo*
-
-* Add `Application#message_verifier` method to return a message verifier.
-
- This verifier can be used to generate and verify signed messages in the application.
-
- message = Rails.application.message_verifier(:sensitive_data).generate('my sensible data')
- Rails.application.message_verifier(:sensitive_data).verify(message)
- # => 'my sensible data'
-
- It is recommended not to use the same verifier for different things, so you can get different
- verifiers passing the name argument.
-
- message = Rails.application.message_verifier(:cookies).generate('my sensible cookie data')
-
- See the `ActiveSupport::MessageVerifier` documentation for more information.
+ Fixes #14748.
*Rafael Mendonça França*
-* The [Spring application
- preloader](https://github.com/jonleighton/spring) is now installed
- by default for new applications. It uses the development group of
- the Gemfile, so will not be installed in production.
-
- *Jon Leighton*
-
-* Uses .railsrc while creating new plugin if it is available.
- Fixes #10700.
+* Move configuration of asset precompile list and version to an initializer.
- *Prathamesh Sonpatki*
-
-* Remove turbolinks when generating a new application based on a template that skips it.
-
- Example:
+ *Matthew Draper*
- Skips turbolinks adding `add_gem_entry_filter { |gem| gem.name != "turbolinks" }`
- to the template.
+* Do not set the Rails environment to test by default when using test_unit Railtie.
- *Lauro Caetano*
+ *Konstantin Shabanov*
-* Instrument an `load_config_initializer.railties` event on each load of configuration initializer
- from `config/initializers`. Subscribers should be attached before `load_config_initializers`
- initializer completed.
+* Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3.
- Registering subscriber examples:
+ *Dmitrii Golub*
- # config/application.rb
- module RailsApp
- class Application < Rails::Application
- ActiveSupport::Notifications.subscribe('load_config_initializer.railties') do |*args|
- event = ActiveSupport::Notifications::Event.new(*args)
- puts "Loaded initializer #{event.payload[:initializer]} (#{event.duration}ms)"
- end
- end
- end
-
- # my_engine/lib/my_engine/engine.rb
- module MyEngine
- class Engine < ::Rails::Engine
- config.before_initialize do
- ActiveSupport::Notifications.subscribe('load_config_initializer.railties') do |*args|
- event = ActiveSupport::Notifications::Event.new(*args)
- puts "Loaded initializer #{event.payload[:initializer]} (#{event.duration}ms)"
- end
- end
- end
- end
-
- *Paul Nikitochkin*
-
-* Support for Pathnames in eager load paths.
-
- *Mike Pack*
-
-* Fixed missing line and shadow on service pages(404, 422, 500).
-
- *Dmitry Korotkov*
-
-* `BACKTRACE` environment variable to show unfiltered backtraces for
- test failures.
+* Add public API to register new extensions for `rake notes`.
Example:
- $ BACKTRACE=1 ruby -Itest ...
- # or with rake
- $ BACKTRACE=1 bin/rake
+ config.annotations.register_extensions("scss", "sass") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
- *Yves Senn*
+ *Roberto Miranda*
-* Removal of all javascript stuff (gems and files) when generating a new
- application using the `--skip-javascript` option.
+* Removed unnecessary `rails application` command.
- *Robin Dupret*
+ *Arun Agrawal*
-* Make the application name snake cased when it contains spaces
+* Make the `rails:template` rake task load the application's initializers.
- The application name is used to fill the `database.yml` and
- `session_store.rb` files ; previously, if the provided name
- contained whitespaces, it led to unexpected names in these files.
+ Fixes #12133.
*Robin Dupret*
-* Added `--model-name` option to `ScaffoldControllerGenerator`.
-
- *yalab*
-
-* Expose MiddlewareStack#unshift to environment configuration.
-
- *Ben Pickles*
-
-* `rails server` will only extend the logger to output to STDOUT
- in development environment.
-
- *Richard Schneeman*
-
-* Don't require passing path to app before options in `rails new`
- and `rails plugin new`
-
- *Piotr Sarnacki*
-
-* rake notes now searches *.less files
-
- *Josh Crowder*
-
-* Generate nested route for namespaced controller generated using
- `rails g controller`.
- Fixes #11532.
+* Introduce `Rails.gem_version` as a convenience method to return
+ `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform
+ version comparison.
Example:
- rails g controller admin/dashboard index
-
- # Before:
- get "dashboard/index"
-
- # After:
- namespace :admin do
- get "dashboard/index"
- end
-
- *Prathamesh Sonpatki*
-
-* Fix the event name of action_dispatch requests.
-
- *Rafael Mendonça França*
-
-* Make `config.log_level` work with custom loggers.
-
- *Max Shytikov*
-
-* Changed stylesheet load order in the stylesheet manifest generator.
- Fixes #11639.
-
- *Pawel Janiak*
-
-* Added generated unit test for generator generator using new
- `test:generators` rake task.
-
- *Josef Šimánek*
-
-* Removed `update:application_controller` rake task.
-
- *Josef Šimánek*
-
-* Fix `rake environment` to do not eager load modules
+ Rails.version #=> "4.1.2"
+ Rails.gem_version #=> #<Gem::Version "4.1.2">
- *Paul Nikitochkin*
+ 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
-* Fix `rake notes` to look into `*.sass` files
-
- *Yuri Artemev*
-
-* Removed deprecated `Rails.application.railties.engines`.
-
- *Arun Agrawal*
-
-* Removed deprecated threadsafe! from Rails Config.
-
- *Paul Nikitochkin*
-
-* Remove deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
- favor of `ActiveRecord::Generators::ActiveModel#update`.
-
- *Vipul A M*
-
-* Remove deprecated `config.whiny_nils` option.
-
- *Vipul A M*
-
-* Rename `commands/plugin_new.rb` to `commands/plugin.rb` and fix references
-
- *Richard Schneeman*
-
-* Fix `rails plugin --help` command.
-
- *Richard Schneeman*
-
-* Omit turbolinks configuration completely on skip_javascript generator option.
-
- *Nikita Fedyashev*
-
-* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and
- `rake test:recent`.
-
- *John Wang*
-
-* Clearing autoloaded constants triggers routes reloading.
- Fixes #10685.
-
- *Xavier Noria*
-
-* Fixes bug with scaffold generator with `--assets=false --resource-route=false`.
- Fixes #9525.
-
- *Arun Agrawal*
+ *Prem Sichanugrist*
-* Rails::Railtie no longer forces the Rails::Configurable module on everything
- that subclasses it. Instead, the methods from Rails::Configurable have been
- moved to class methods in Railtie and the Railtie has been made abstract.
+* Avoid namespacing routes inside engines.
- *John Wang*
+ Mountable engines are namespaced by default so the generated routes
+ were too while they should not.
-* Changes repetitive th tags to use colspan attribute in `index.html.erb` template.
+ Fixes #14079.
- *Sıtkı Bağdat*
+ *Yves Senn*, *Carlos Antonio da Silva*, *Robin Dupret*
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index be7570a5ba..ecd8c22dd8 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -80,10 +80,6 @@ module Rails
groups
end
- def version
- VERSION::STRING
- end
-
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 1610751844..56f05b3844 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -55,7 +55,7 @@ EOS
end
def self.find_executable
- EXECUTABLES.find { |exe| File.exist?(exe) }
+ EXECUTABLES.find { |exe| File.file?(exe) }
end
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 05acd78d98..2fde974732 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -87,7 +87,7 @@ module Rails
class << self
def inherited(base)
super
- Rails.application ||= base.instance
+ base.instance
end
# Makes the +new+ method public.
@@ -117,6 +117,8 @@ module Rails
@railties = nil
@message_verifiers = {}
+ Rails.application ||= self
+
add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, self)
@@ -151,14 +153,13 @@ module Rails
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
- @caching_key_generator ||= begin
+ @caching_key_generator ||=
if secrets.secret_key_base
key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
ActiveSupport::LegacyKeyGenerator.new(config.secret_token)
end
- end
end
# Returns a message verifier object.
@@ -205,7 +206,8 @@ module Rails
"action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt,
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
- "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
+ "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
+ "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer
})
end
end
@@ -229,6 +231,18 @@ module Rails
self.class.runner(&blk)
end
+ # Sends any console called in the instance of a new application up
+ # to the +console+ method defined in Rails::Railtie.
+ def console(&blk)
+ self.class.console(&blk)
+ end
+
+ # Sends any generators called in the instance of a new application up
+ # to the +generators+ method defined in Rails::Railtie.
+ def generators(&blk)
+ self.class.generators(&blk)
+ end
+
# Sends the +isolate_namespace+ method up to the class method.
def isolate_namespace(mod)
self.class.isolate_namespace(mod)
@@ -307,7 +321,8 @@ module Rails
yaml = config.paths["config/secrets"].first
if File.exist?(yaml)
require "erb"
- env_secrets = YAML.load(ERB.new(IO.read(yaml)).result)[Rails.env]
+ all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
+ env_secrets = all_secrets[Rails.env]
secrets.merge!(env_secrets.symbolize_keys) if env_secrets
end
@@ -330,6 +345,25 @@ module Rails
config.helpers_paths
end
+ console do
+ require "pp"
+ end
+
+ 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
+ end
+ end
+
protected
alias :build_middleware_stack :app
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 33bcab1e57..a26d41c0cf 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -53,11 +53,7 @@ INFO
logger
end
- if ::Logger === Rails.logger
- Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
- else
- Rails.logger.level = config.log_level
- end
+ Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
end
# Initialize cache early in the stack so railties can make use of it.
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index e902205a13..4c449d2c57 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/kernel/reporting'
require 'active_support/file_update_checker'
require 'rails/engine/configuration'
+require 'rails/source_annotation_extractor'
module Rails
class Application
@@ -94,6 +95,7 @@ module Rails
yaml = Pathname.new(paths["config/database"].first || "")
config = if yaml.exist?
+ require "yaml"
require "erb"
YAML.load(ERB.new(yaml.read).result) || {}
elsif ENV['DATABASE_URL']
@@ -109,6 +111,8 @@ module Rails
raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{e.message}"
+ rescue => e
+ raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
end
def log_level
@@ -147,6 +151,9 @@ module Rails
end
end
+ def annotations
+ SourceAnnotationExtractor::Annotation
+ end
end
end
end
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index de60423784..6cfbc70c51 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -20,7 +20,6 @@ The most common rails commands are:
new application called MyApp in "./my_app"
In addition to those, there are:
- application Generate the Rails application code
destroy Undo code generated with "generate" (short-cut alias: "d")
plugin new Generates skeleton for developing a Rails plugin
runner Run a piece of code in the application environment (short-cut alias: "r")
@@ -28,7 +27,7 @@ In addition to those, there are:
All commands can be run with -h (or --help) for more information.
EOT
- COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help)
+ COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help)
def initialize(argv)
@argv = argv
@@ -87,10 +86,6 @@ EOT
Rails::DBConsole.start
end
- def application
- require_command!("application")
- end
-
def runner
require_command!("runner")
end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index f6bdf129d6..555d8f31e1 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -18,7 +18,14 @@ 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", 'Enable the debugger.') { |v| options[:debugger] = v }
+ opt.on("--debugger", 'Enable 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
@@ -69,12 +76,25 @@ module Rails
Rails.env = environment
end
- def debugger?
- options[:debugger]
+ 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
- require_debugger if debugger?
+ if RUBY_VERSION < '2.0.0'
+ require_debugger if debugger?
+ end
+
set_environment! if environment?
if sandbox?
@@ -89,13 +109,5 @@ module Rails
end
console.start
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
end
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index f7a0b99005..95bbdd4cdf 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+/).map {|l| l.split}.flatten
+ extra_args = extra_args_string.split(/\n+/).flat_map {|l| l.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 fec4962fb5..6146b6c1db 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -18,7 +18,14 @@ module Rails
opts.on("-c", "--config=file", String,
"Use custom rackup configuration file") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true }
+ opts.on("-u", "--debugger", "Enable 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 }
@@ -75,7 +82,9 @@ module Rails
def middleware
middlewares = []
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
+ 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.
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 5c54cdaa70..b36ab3d0d5 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -371,7 +371,7 @@ module Rails
end
def isolate_namespace(mod)
- engine_name(generate_railtie_name(mod))
+ engine_name(generate_railtie_name(mod.name))
self.routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
@@ -429,7 +429,6 @@ module Rails
# Load console and invoke the registered hooks.
# Check <tt>Rails::Railtie.console</tt> for more info.
def load_console(app=self)
- require "pp"
require "rails/console/app"
require "rails/console/helpers"
run_console_blocks(app)
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
new file mode 100644
index 0000000000..c7397c4f15
--- /dev/null
+++ b/railties/lib/rails/gem_version.rb
@@ -0,0 +1,15 @@
+module Rails
+ # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index afdbf5c241..625f031c94 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -188,7 +188,7 @@ module Rails
# generate(:authenticated, "user session")
def generate(what, *args)
log :generate, what
- argument = args.map {|arg| arg.to_s }.flatten.join(" ")
+ argument = args.flat_map {|arg| arg.to_s }.join(" ")
in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) }
end
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
new file mode 100644
index 0000000000..cf3b7acfff
--- /dev/null
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -0,0 +1,68 @@
+require 'thor/actions'
+
+module Rails
+ module Generators
+ module Actions
+ class CreateMigration < Thor::Actions::CreateFile
+
+ def migration_dir
+ File.dirname(@destination)
+ end
+
+ def migration_file_name
+ @base.migration_file_name
+ end
+
+ def identical?
+ exists? && File.binread(existing_migration) == render
+ end
+
+ def revoke!
+ say_destination = exists? ? relative_existing_migration : relative_destination
+ say_status :remove, :red, say_destination
+ return unless exists?
+ ::FileUtils.rm_rf(existing_migration) unless pretend?
+ existing_migration
+ end
+
+ def relative_existing_migration
+ base.relative_to_original_destination_root(existing_migration)
+ end
+
+ def existing_migration
+ @existing_migration ||= begin
+ @base.class.migration_exists?(migration_dir, migration_file_name) ||
+ File.exist?(@destination) && @destination
+ end
+ end
+ alias :exists? :existing_migration
+
+ protected
+
+ def on_conflict_behavior(&block)
+ options = base.options.merge(config)
+ if identical?
+ say_status :identical, :blue, relative_existing_migration
+ elsif options[:force]
+ say_status :remove, :green, relative_existing_migration
+ say_status :create, :green
+ unless pretend?
+ ::FileUtils.rm_rf(existing_migration)
+ block.call
+ end
+ elsif options[:skip]
+ say_status :skip, :yellow
+ else
+ say_status :conflict, :red
+ raise Error, "Another migration is already named #{migration_file_name}: " +
+ "#{existing_migration}. Use --force to replace this migration file."
+ end
+ end
+
+ def say_status(status, color, message = relative_destination)
+ base.shell.say_status(status, message, color) if config[:verbose]
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 1b50569c9e..569afe8104 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -14,7 +14,6 @@ module Rails
DATABASES.concat(JDBC_DATABASES)
attr_accessor :rails_template
- attr_accessor :app_template
add_shebang_option!
argument :app_path, type: :string
@@ -27,9 +26,6 @@ module Rails
class_option :template, type: :string, aliases: '-m',
desc: "Path to some #{name} template (can be a filesystem path or URL)"
- class_option :app_template, type: :string, aliases: '-n',
- desc: "Path to some #{name} template (can be a filesystem path or URL)"
-
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
@@ -83,7 +79,6 @@ module Rails
end
def initialize(*args)
- @original_wd = Dir.pwd
@gem_filter = lambda { |gem| true }
@extra_entries = []
super
@@ -115,7 +110,6 @@ module Rails
javascript_gemfile_entry,
jbuilder_gemfile_entry,
sdoc_gemfile_entry,
- platform_dependent_gemfile_entry,
spring_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -126,10 +120,6 @@ module Rails
}.curry[@gem_filter]
end
- def remove_gem(name)
- add_gem_entry_filter { |gem| gem.name != name }
- end
-
def builder
@builder ||= begin
builder_class = get_builder_class
@@ -149,92 +139,21 @@ module Rails
FileUtils.cd(destination_root) unless options[:pretend]
end
- class TemplateRecorder < ::BasicObject # :nodoc:
- attr_reader :gems
-
- def initialize(target)
- @target = target
- # unfortunately, instance eval has access to these ivars
- @app_const = target.send :app_const if target.respond_to?(:app_const, true)
- @app_const_base = target.send :app_const_base if target.respond_to?(:app_const_base, true)
- @app_name = target.send :app_name if target.respond_to?(:app_name, true)
- @commands = []
- @gems = []
- end
-
- def gemfile_entry(*args)
- @target.send :gemfile_entry, *args
- end
-
- def add_gem_entry_filter(*args, &block)
- @target.send :add_gem_entry_filter, *args, &block
- end
-
- def remove_gem(*args, &block)
- @target.send :remove_gem, *args, &block
- end
-
- def method_missing(name, *args, &block)
- @commands << [name, args, block]
- end
-
- def respond_to_missing?(method, priv = false)
- super || @target.respond_to?(method, priv)
- end
-
- def replay!
- @commands.each do |name, args, block|
- @target.send name, *args, &block
- end
- end
- end
-
def apply_rails_template
- @recorder = TemplateRecorder.new self
-
- apply(rails_template, target: self) if rails_template
- apply(app_template, target: @recorder) if app_template
+ apply rails_template if rails_template
rescue Thor::Error, LoadError, Errno::ENOENT => e
raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
- def replay_template
- @recorder.replay! if @recorder
- end
-
- def apply(path, config={})
- verbose = config.fetch(:verbose, true)
- target = config.fetch(:target, self)
- is_uri = path =~ /^https?\:\/\//
- path = find_in_source_paths(path) unless is_uri
-
- say_status :apply, path, verbose
- shell.padding += 1 if verbose
-
- if is_uri
- contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
- else
- contents = open(path) {|io| io.read }
- end
-
- target.instance_eval(contents, path)
- shell.padding -= 1 if verbose
- end
-
def set_default_accessors!
self.destination_root = File.expand_path(app_path, destination_root)
- self.rails_template = expand_template options[:template]
- self.app_template = expand_template options[:app_template]
- end
-
- def expand_template(name)
- case name
- when /^https?:\/\//
- name
- when String
- File.expand_path(name, Dir.pwd)
- else
- name
+ self.rails_template = case options[:template]
+ when /^https?:\/\//
+ options[:template]
+ when String
+ File.expand_path(options[:template], Dir.pwd)
+ else
+ options[:template]
end
end
@@ -252,6 +171,10 @@ module Rails
options[value] ? '# ' : ''
end
+ def sqlite3?
+ !options[:skip_active_record] && options[:database] == 'sqlite3'
+ end
+
class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out)
def initialize(name, version, comment, options = {}, commented_out = false)
super
@@ -326,7 +249,7 @@ module Rails
'Use SCSS for stylesheets')
else
gems << GemfileEntry.version('sass-rails',
- '~> 4.0.1',
+ '~> 4.0.3',
'Use SCSS for stylesheets')
end
@@ -337,14 +260,6 @@ module Rails
gems
end
- def platform_dependent_gemfile_entry
- gems = []
- if RUBY_ENGINE == 'rbx'
- gems << GemfileEntry.version('rubysl', nil)
- end
- gems
- end
-
def jbuilder_gemfile_entry
comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
GemfileEntry.version('jbuilder', '~> 2.0', comment)
@@ -389,7 +304,7 @@ module Rails
def spring_gemfile_entry
return [] unless spring_install?
- comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring'
+ comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring'
GemfileEntry.new('spring', nil, comment, group: :development)
end
@@ -412,7 +327,8 @@ module Rails
require 'bundler'
Bundler.with_clean_env do
- print `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
+ output = `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
+ print output unless options[:quiet]
end
end
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index cfd77097d5..0755ac335c 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -6,7 +6,7 @@ module Erb # :nodoc:
protected
def formats
- format
+ [format]
end
def format
@@ -17,7 +17,7 @@ module Erb # :nodoc:
:erb
end
- def filename_with_extensions(name, format)
+ def filename_with_extensions(name, format = self.format)
[name, format, handler].compact.join(".")
end
end
diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb
index e62aece7c5..94c1b835d1 100644
--- a/railties/lib/rails/generators/erb/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb
@@ -11,7 +11,7 @@ module Erb # :nodoc:
actions.each do |action|
@action = action
- Array(formats).each do |format|
+ formats.each do |format|
@path = File.join(base_path, filename_with_extensions(action, format))
template filename_with_extensions(:view, format), @path
end
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
index 8bb7c2b768..b5045671b3 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
@@ -1,5 +1,5 @@
<h1><%= class_name %>#<%= @action %></h1>
<p>
- <%%= @greeting %>, find me in app/views/<%= @path %>
+ <%%= @greeting %>, find me in <%= @path %>
</p>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
index 6d597256a6..342285df19 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
@@ -1,3 +1,3 @@
<%= class_name %>#<%= @action %>
-<%%= @greeting %>, find me in app/views/<%= @path %>
+<%%= @greeting %>, find me in <%= @path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index b219f459ac..c94829a0ae 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -14,7 +14,7 @@ module Erb # :nodoc:
def copy_view_files
available_views.each do |view|
- Array(formats).each do |format|
+ formats.each do |format|
filename = filename_with_extensions(view, format)
template filename, File.join("app/views", controller_file_path, filename)
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 69c10efa47..da99e74435 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -4,8 +4,8 @@
<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 |msg| %>
- <li><%%= msg %></li>
+ <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %>
+ <li><%%= message %></li>
<%% end %>
</ul>
</div>
@@ -21,13 +21,8 @@
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
<% else -%>
- <%- if attribute.reference? -%>
<%%= f.label :<%= attribute.column_name %> %><br>
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
- <%- else -%>
- <%%= f.label :<%= attribute.name %> %><br>
- <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
- <%- end -%>
<% end -%>
</div>
<% end -%>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 5e2784c4b0..c5326d70d1 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -94,6 +94,10 @@ module Rails
name.sub(/_id$/, '').pluralize
end
+ def singular_name
+ name.sub(/_id$/, '').singularize
+ end
+
def human_name
name.humanize
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 3566f96f5e..cd388e590a 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'rails/generators/actions/create_migration'
module Rails
module Generators
@@ -29,6 +30,19 @@ module Rails
end
end
+ def create_migration(destination, data, config = {}, &block)
+ action Rails::Generators::Actions::CreateMigration.new(self, destination, block || data.to_s, config)
+ end
+
+ def set_migration_assigns!(destination)
+ destination = File.expand_path(destination, self.destination_root)
+
+ migration_dir = File.dirname(destination)
+ @migration_number = self.class.next_migration_number(migration_dir)
+ @migration_file_name = File.basename(destination, '.rb')
+ @migration_class_name = @migration_file_name.camelize
+ end
+
# Creates a migration template at the given destination. The difference
# to the default template method is that the migration version is appended
# to the destination file name.
@@ -37,26 +51,18 @@ module Rails
# available as instance variables in the template to be rendered.
#
# migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb"
- def migration_template(source, destination=nil, config={})
- destination = File.expand_path(destination || source, self.destination_root)
+ def migration_template(source, destination, config = {})
+ source = File.expand_path(find_in_source_paths(source.to_s))
- migration_dir = File.dirname(destination)
- @migration_number = self.class.next_migration_number(migration_dir)
- @migration_file_name = File.basename(destination).sub(/\.rb$/, '')
- @migration_class_name = @migration_file_name.camelize
+ set_migration_assigns!(destination)
+ context = instance_eval('binding')
- destination = self.class.migration_exists?(migration_dir, @migration_file_name)
+ dir, base = File.split(destination)
+ numbered_destination = File.join(dir, ["%migration_number%", base].join('_'))
- if !(destination && options[:skip]) && behavior == :invoke
- if destination && options.force?
- remove_file(destination)
- elsif destination
- raise Error, "Another migration is already named #{@migration_file_name}: #{destination}. Use --force to remove the old migration file and replace it."
- end
- destination = File.join(migration_dir, "#{@migration_number}_#{@migration_file_name}.rb")
+ create_migration numbered_destination, nil, config do
+ ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
end
-
- template(source, destination, config)
end
end
end
diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb
new file mode 100644
index 0000000000..42c646543e
--- /dev/null
+++ b/railties/lib/rails/generators/model_helpers.rb
@@ -0,0 +1,28 @@
+require 'rails/generators/active_model'
+
+module Rails
+ module Generators
+ module ModelHelpers # :nodoc:
+ PLURAL_MODEL_NAME_WARN_MESSAGE = "[WARNING] The model name '%s' was recognized as a plural, using the singular '%s' instead. " \
+ "Override with --force-plural or setup custom inflection rules for this noun before running the generator."
+ mattr_accessor :skip_warn
+
+ def self.included(base) #:nodoc:
+ base.class_option :force_plural, type: :boolean, default: false, desc: 'Forces the use of the given model name'
+ end
+
+ def initialize(args, *_options)
+ super
+ if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural]
+ singular = name.singularize
+ unless ModelHelpers.skip_warn
+ say PLURAL_MODEL_NAME_WARN_MESSAGE % [name, singular]
+ ModelHelpers.skip_warn = true
+ end
+ name.replace singular
+ assign_names!(name)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index d2eca5b2fb..8675d8bc1e 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -50,7 +50,7 @@ module Rails
end
def gitignore
- copy_file "gitignore", ".gitignore"
+ template "gitignore", ".gitignore"
end
def app
@@ -166,7 +166,6 @@ module Rails
end
public_task :set_default_accessors!
- public_task :apply_rails_template
public_task :create_root
def create_root_files
@@ -232,12 +231,17 @@ module Rails
end
end
+ def delete_assets_initializer_skipping_sprockets
+ if options[:skip_sprockets]
+ remove_file 'config/initializers/assets.rb'
+ end
+ end
+
def finish_template
build(:leftovers)
end
- public_task :run_bundle
- public_task :replay_template
+ public_task :apply_rails_template, :run_bundle
public_task :generate_spring_binstubs
protected
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 6d017e187d..448b6f4845 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -14,7 +14,7 @@ source 'https://rubygems.org'
<% end -%>
# Use ActiveModel has_secure_password
-# gem 'bcrypt-ruby', '~> 3.1.2'
+# gem 'bcrypt', '~> 3.1.7'
# Use unicorn as the app server
# gem 'unicorn'
@@ -23,11 +23,15 @@ source 'https://rubygems.org'
# gem 'capistrano-rails', group: :development
<% unless defined?(JRUBY_VERSION) -%>
-# Use debugger
+# 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/) -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem 'tzinfo-data', platforms: [:mingw, :mswin]
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index fe71f7122c..75ea52828e 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -3,14 +3,14 @@
<head>
<title><%= camelized %></title>
<%- if options[:skip_javascript] -%>
- <%%= stylesheet_link_tag "application", media: "all" %>
+ <%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
- <%- if gemfile_entries.any? { |m| m.name == "turbolinks" } -%>
- <%%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
- <%%= javascript_include_tag "application", "data-turbolinks-track" => true %>
+ <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
+ <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
+ <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%- else -%>
- <%%= stylesheet_link_tag "application", media: "all" %>
- <%%= javascript_include_tag "application" %>
+ <%%= stylesheet_link_tag 'application', media: 'all' %>
+ <%%= javascript_include_tag 'application' %>
<%- end -%>
<%- end -%>
<%%= csrf_meta_tags %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
index 138e3a8664..34fc0e3465 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
@@ -23,7 +23,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="frontbase://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
index 2cdb592eeb..d088dd62bf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
@@ -61,7 +61,27 @@ test:
<<: *default
database: <%= app_name[0,4] %>_tst
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="ibm-db://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
index cefd30d519..db0a429753 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
@@ -53,7 +53,16 @@ test:
<<: *default
url: jdbc:db://localhost/<%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ url: jdbc:db://localhost/<%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
index d31761349c..acb93939e1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -26,6 +26,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
-production: <%%= ENV["DATABASE_URL"] %>
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
+production:
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
index 0d248dc197..9e99264d33 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -42,7 +42,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
index 66eba3bf0d..28c36eb82f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
@@ -18,7 +18,6 @@ test:
<<: *default
database: db/test.sqlite3
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: db/production.sqlite3
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 d618fc28a6..4b2e6646c7 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
@@ -32,11 +32,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Avoid production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
#
-# Example:
-# mysql2://myuser:mypass@localhost/somedatabase
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index d469ec0f99..10ab4c02e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -32,7 +32,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index 93f48656b2..feb25bbc6b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -59,11 +59,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
#
-# Example:
-# postgres://myuser:mypass@localhost/somedatabase
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
index 7312ddb6cd..1c1a37ca8d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
@@ -20,11 +20,6 @@ test:
<<: *default
database: db/test.sqlite3
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
-#
-# Example:
-# sqlite3://myuser:mypass@localhost/full/path/to/somedatabase
-#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: db/production.sqlite3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
index aa960e493e..30b0df34a8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
@@ -42,7 +42,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="sqlserver://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
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 cce4743a33..de12565a73 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
@@ -35,4 +35,7 @@ Rails.application.configure do
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
<%- end -%>
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
end
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 3baa382bd6..9ed71687ea 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
@@ -5,7 +5,7 @@ Rails.application.configure do
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
- # your application in memory, allowing both thread web servers
+ # your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
@@ -33,8 +33,7 @@ Rails.application.configure do
# Generate digests for assets URLs.
config.assets.digest = true
- # Version of your assets, change this if you want to expire all your assets.
- config.assets.version = '1.0'
+ # `config.assets.precompile` has moved to config/initializers/assets.rb
<%- end -%>
# Specifies the header that your server uses for sending files.
@@ -59,12 +58,6 @@ 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_sprockets? -%>
- # Precompile additional assets.
- # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
- # config.assets.precompile += %w( search.js )
- <%- end -%>
-
# 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
@@ -81,4 +74,9 @@ Rails.application.configure do
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
+ <%- unless options.skip_active_record? -%>
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+ <%- end -%>
end
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 ba0742f97f..053f5b66d7 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
@@ -14,7 +14,7 @@ Rails.application.configure do
# Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
- config.static_cache_control = "public, max-age=3600"
+ config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
config.consider_all_requests_local = true
@@ -33,4 +33,7 @@ Rails.application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
new file mode 100644
index 0000000000..d2f4ec33a6
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
+# Rails.application.config.assets.precompile += %w( search.js )
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
new file mode 100644
index 0000000000..7f70458dee
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
@@ -0,0 +1,3 @@
+# Be sure to restart your server when you modify this file.
+
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
index 72aca7e441..dc1899682b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
@@ -2,4 +2,3 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
-# Mime::Type.register_alias "text/html", :iphone
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 6a502e997f..8775e5e235 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -7,10 +7,12 @@
# Ignore bundler config.
/.bundle
+<% if sqlite3? -%>
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
+<% end -%>
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index 4ade1a0bdc..6b011e577a 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -1,4 +1,4 @@
-ENV["RAILS_ENV"] ||= "test"
+ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 33a0d81bf6..7588a558e7 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -27,11 +27,11 @@ module Rails
# end
# end
def generate_routing_code(action)
- depth = class_path.length
+ depth = regular_class_path.length
# Create 'namespace' ladder
# namespace :foo do
# namespace :bar do
- namespace_ladder = class_path.each_with_index.map do |ns, i|
+ namespace_ladder = regular_class_path.each_with_index.map do |ns, i|
indent("namespace :#{ns} do\n", i * 2)
end.join
diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb
index ea3d69d7c9..87bab129bb 100644
--- a/railties/lib/rails/generators/rails/model/model_generator.rb
+++ b/railties/lib/rails/generators/rails/model/model_generator.rb
@@ -1,6 +1,10 @@
+require 'rails/generators/model_helpers'
+
module Rails
module Generators
class ModelGenerator < NamedBase # :nodoc:
+ include Rails::Generators::ModelHelpers
+
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
hook_for :orm, required: true
end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index dbe1e37d8e..f6f529b80a 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -185,7 +185,6 @@ task default: :test
end
public_task :set_default_accessors!
- public_task :apply_rails_template
public_task :create_root
def create_root_files
@@ -242,6 +241,7 @@ task default: :test
build(:leftovers)
end
+ public_task :apply_rails_template, :run_bundle
def name
@name ||= begin
@@ -255,9 +255,6 @@ task default: :test
end
end
- public_task :run_bundle
- public_task :replay_template
-
protected
def app_templates_dir
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index f0a832f783..1f704db510 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -38,6 +38,10 @@ end
<% end -%>
<% unless defined?(JRUBY_VERSION) -%>
-# To use debugger
-# gem 'debugger'
+# To use a debugger
+ <%- if RUBY_VERSION < '2.0.0' -%>
+# gem 'debugger', group: [:development, :test]
+ <%- else -%>
+# gem 'byebug', group: [:development, :test]
+ <%- end -%>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
index c8de9f3e0f..c3314d7e68 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -3,5 +3,9 @@
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__)
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+
require 'rails/all'
require 'rails/engine/commands'
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 0e69aa101f..2c3b04043f 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -31,7 +31,7 @@ class <%= controller_class_name %>Controller < ApplicationController
if @<%= orm_instance.save %>
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
else
- render action: 'new'
+ render :new
end
end
@@ -40,7 +40,7 @@ class <%= controller_class_name %>Controller < ApplicationController
if @<%= orm_instance.update("#{singular_table_name}_params") %>
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
else
- render action: 'edit'
+ render :edit
end
end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index a01eb57651..4669935156 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -1,35 +1,24 @@
require 'rails/generators/active_model'
+require 'rails/generators/model_helpers'
module Rails
module Generators
# Deal with controller names on scaffold and add some helpers to deal with
# ActiveModel.
module ResourceHelpers # :nodoc:
- mattr_accessor :skip_warn
def self.included(base) #:nodoc:
- base.class_option :force_plural, type: :boolean, desc: "Forces the use of a plural ModelName"
+ base.send :include, Rails::Generators::ModelHelpers
base.class_option :model_name, type: :string, desc: "ModelName to be used"
end
# Set controller variables on initialization.
def initialize(*args) #:nodoc:
super
+ controller_name = name
if options[:model_name]
- controller_name = name
self.name = options[:model_name]
assign_names!(self.name)
- else
- controller_name = name
- end
-
- if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural]
- unless ResourceHelpers.skip_warn
- say "Plural version of the model detected, using singularized version. Override with --force-plural."
- ResourceHelpers.skip_warn = true
- end
- name.replace name.singularize
- assign_names!(name)
end
assign_controller_names!(controller_name.pluralize)
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index edadeaca0e..9502876ebb 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -23,7 +23,7 @@ module Rails
end
def frameworks
- %w( active_record action_pack action_view action_mailer active_support )
+ %w( active_record action_pack action_view action_mailer active_support active_model )
end
def framework_version(framework)
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 117bb37487..3eb66c07af 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -101,7 +101,7 @@ module Rails
def filter_by(&block)
all_paths.find_all(&block).flat_map { |path|
paths = path.existent
- paths - path.children.map { |p| yield(p) ? [] : p.existent }.flatten
+ paths - path.children.flat_map { |p| yield(p) ? [] : p.existent }
}.uniq
end
end
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index d1ee96f7fd..886f0e52e1 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -1,6 +1,6 @@
module Rails
module Rack
- autoload :Debugger, "rails/rack/debugger"
+ autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0'
autoload :Logger, "rails/rack/logger"
autoload :LogTailer, "rails/rack/log_tailer"
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index c63e0c0758..2b33beaa2b 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -183,8 +183,8 @@ module Rails
end
protected
- def generate_railtie_name(class_or_module)
- ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_")
+ def generate_railtie_name(string)
+ ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
# If the class method does not have a method, then send the method call
@@ -221,26 +221,28 @@ module Rails
protected
def run_console_blocks(app) #:nodoc:
- self.class.console.each { |block| block.call(app) }
+ each_registered_block(:console) { |block| block.call(app) }
end
def run_generators_blocks(app) #:nodoc:
- self.class.generators.each { |block| block.call(app) }
+ each_registered_block(:generators) { |block| block.call(app) }
end
def run_runner_blocks(app) #:nodoc:
- self.class.runner.each { |block| block.call(app) }
+ each_registered_block(:runner) { |block| block.call(app) }
end
def run_tasks_blocks(app) #:nodoc:
extend Rake::DSL
- self.class.rake_tasks.each { |block| instance_exec(app, &block) }
+ each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) }
+ end
- # Load also tasks from all superclasses
- klass = self.class.superclass
+ private
- while klass.respond_to?(:rake_tasks)
- klass.rake_tasks.each { |t| instance_exec(app, &t) }
+ def each_registered_block(type, &block)
+ klass = self.class
+ while klass.respond_to?(type)
+ klass.public_send(type).each(&block)
klass = klass.superclass
end
end
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 3cf6a005ea..201532d299 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -18,6 +18,20 @@ class SourceAnnotationExtractor
@@directories ||= %w(app config db lib test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
end
+ def self.extensions
+ @@extensions ||= {}
+ end
+
+ # Registers new Annotations File Extensions
+ # SourceAnnotationExtractor::Annotation.register_extensions("css", "scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
+ def self.register_extensions(*exts, &block)
+ extensions[/\.(#{exts.join("|")})$/] = block
+ end
+
+ register_extensions("builder", "rb", "rake", "yml", "yaml", "ruby") { |tag| /#\s*(#{tag}):?\s*(.*)$/ }
+ register_extensions("css", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
+ register_extensions("erb") { |tag| /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ }
+
# Returns a representation of the annotation that looks like this:
#
# [126] [TODO] This algorithm is simple and clearly correct, make it faster.
@@ -78,21 +92,14 @@ class SourceAnnotationExtractor
if File.directory?(item)
results.update(find_in(item))
else
- pattern =
- case item
- when /\.(builder|rb|coffee|rake)$/
- /#\s*(#{tag}):?\s*(.*)$/
- when /\.(css|scss|sass|less|js)$/
- /\/\/\s*(#{tag}):?\s*(.*)$/
- when /\.erb$/
- /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/
- when /\.haml$/
- /-\s*#\s*(#{tag}):?\s*(.*)$/
- when /\.slim$/
- /\/\s*\s*(#{tag}):?\s*(.*)$/
- else nil
- end
- results.update(extract_annotations_from(item, pattern)) if pattern
+ extension = Annotation.extensions.detect do |regexp, _block|
+ regexp.match(item)
+ end
+
+ if extension
+ pattern = extension.last.call(tag)
+ results.update(extract_annotations_from(item, pattern)) if pattern
+ end
end
end
@@ -115,7 +122,7 @@ class SourceAnnotationExtractor
# Prints the mapping from filenames to annotations in +results+ ordered by filename.
# The +options+ hash is passed to each annotation's +to_s+.
def display(results, options={})
- options[:indent] = results.map { |f, a| a.map(&:line) }.flatten.max.to_s.size
+ options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size
results.keys.sort.each do |file|
puts "#{file}:"
results[file].each do |note|
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index e669315934..3c8f8c6b87 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -3,7 +3,7 @@ namespace :rails do
task update: [ "update:configs", "update:bin" ]
desc "Applies the template supplied by LOCATION=(/path/to/template) or URL"
- task :template do
+ task template: :environment do
template = ENV["LOCATION"]
raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank?
template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://}
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 2e6356343d..c837fadb40 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -4,6 +4,7 @@ abort("Abort testing: Your Rails environment is running in production mode!") if
require 'active_support/testing/autorun'
require 'active_support/test_case'
+require 'action_controller'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
require 'rails/generators/test_case'
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 75180ff978..878b9b7930 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,4 +1,4 @@
-if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
+if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any?
ENV['RAILS_ENV'] ||= 'test'
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 923cab4e2a..df351c4238 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,10 +1,8 @@
-module Rails
- module VERSION
- MAJOR = 4
- MINOR = 1
- TINY = 0
- PRE = "beta1"
+require_relative 'gem_version'
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+module Rails
+ # Returns the version of the currently loaded Rails as a string.
+ def self.version
+ VERSION::STRING
end
end
diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb
index 92cb3233d8..1d3b80253a 100644
--- a/railties/test/app_rails_loader_test.rb
+++ b/railties/test/app_rails_loader_test.rb
@@ -22,8 +22,14 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
exe = "#{script_dir}/rails"
test "is not in a Rails application if #{exe} is not found in the current or parent directories" do
- File.stubs(:exist?).with('bin/rails').returns(false)
- File.stubs(:exist?).with('script/rails').returns(false)
+ File.stubs(:file?).with('bin/rails').returns(false)
+ File.stubs(:file?).with('script/rails').returns(false)
+
+ assert !Rails::AppRailsLoader.exec_app_rails
+ end
+
+ test "is not in a Rails application if #{exe} exists but is a folder" do
+ FileUtils.mkdir_p(exe)
assert !Rails::AppRailsLoader.exec_app_rails
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index b235b51d90..410b0f7d70 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -199,6 +199,7 @@ module ApplicationTests
end
test "precompile creates a manifest file with all the assets listed" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
app_file "app/assets/javascripts/application.js", "alert();"
# digest is default in false, we must enable it for test environment
@@ -260,7 +261,7 @@ module ApplicationTests
test "precompile shouldn't use the digests present in manifest.json" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }"
+ app_file "app/assets/stylesheets/application.css.erb", "p { url: <%= asset_path('rails.png') %> }"
ENV["RAILS_ENV"] = "production"
precompile!
@@ -448,23 +449,23 @@ module ApplicationTests
test "asset urls should be protocol-relative if no request is in scope" do
app_file "app/assets/images/rails.png", "notreallyapng"
- app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";'
- add_to_config "config.assets.precompile = %w{image_loader.js}"
+ app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';"
+ add_to_config "config.assets.precompile = %w{rails.png image_loader.js}"
add_to_config "config.asset_host = 'example.com'"
precompile!
- assert_match 'src="//example.com/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first)
+ assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first)
end
test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do
ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"
app_file "app/assets/images/rails.png", "notreallyapng"
- app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";'
- add_to_config "config.assets.precompile = %w{app.js}"
+ app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';"
+ add_to_config "config.assets.precompile = %w{rails.png app.js}"
precompile!
- assert_match 'src="/sub/uri/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/app-*.js"].first)
+ assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first)
end
test "assets:cache:clean should clean cache" do
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 02d8b2c91d..09aba1c2e9 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -336,6 +336,14 @@ module ApplicationTests
assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key
end
+ test "blank config/secrets.yml does not crash the loading process" do
+ app_file 'config/secrets.yml', <<-YAML
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_nil app.secrets.not_defined
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -481,7 +489,7 @@ module ApplicationTests
test "valid timezone is setup correctly" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.time_zone = "Wellington"
+ config.time_zone = "Wellington"
RUBY
require "#{app_path}/config/environment"
@@ -492,7 +500,7 @@ module ApplicationTests
test "raises when an invalid timezone is defined in the config" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.time_zone = "That big hill over yonder hill"
+ config.time_zone = "That big hill over yonder hill"
RUBY
assert_raise(ArgumentError) do
@@ -503,7 +511,7 @@ module ApplicationTests
test "valid beginning of week is setup correctly" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.beginning_of_week = :wednesday
+ config.beginning_of_week = :wednesday
RUBY
require "#{app_path}/config/environment"
@@ -514,7 +522,7 @@ module ApplicationTests
test "raises when an invalid beginning of week is defined in the config" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.beginning_of_week = :invalid
+ config.beginning_of_week = :invalid
RUBY
assert_raise(ArgumentError) do
@@ -754,7 +762,7 @@ module ApplicationTests
end
end
- test "lookup config.log_level with custom logger (stdlib Logger)" do
+ test "config.log_level with custom logger" do
make_basic_app do |app|
app.config.logger = Logger.new(STDOUT)
app.config.log_level = :info
@@ -762,24 +770,114 @@ module ApplicationTests
assert_equal Logger::INFO, Rails.logger.level
end
- test "assign log_level as is with custom logger (third party logger)" do
- logger_class = Class.new do
- attr_accessor :level
- end
- logger_instance = logger_class.new
- make_basic_app do |app|
- app.config.logger = logger_instance
- app.config.log_level = :info
- end
- assert_equal logger_instance, Rails.logger
- assert_equal :info, Rails.logger.level
- end
-
test "respond_to? accepts include_private" do
make_basic_app
assert_not Rails.configuration.respond_to?(:method_missing)
assert Rails.configuration.respond_to?(:method_missing, true)
end
+
+ test "config.active_record.dump_schema_after_migration is false on production" do
+ build_app
+ ENV["RAILS_ENV"] = "production"
+
+ require "#{app_path}/config/environment"
+
+ assert_not ActiveRecord::Base.dump_schema_after_migration
+ end
+
+ test "config.active_record.dump_schema_after_migration is true by default on development" do
+ ENV["RAILS_ENV"] = "development"
+
+ require "#{app_path}/config/environment"
+
+ assert ActiveRecord::Base.dump_schema_after_migration
+ end
+
+ test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do
+ make_basic_app do |app|
+ app.config.annotations.register_extensions("coffee") do |tag|
+ /#\s*(#{tag}):?\s*(.*)$/
+ end
+ end
+
+ assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/]
+ end
+
+ test "rake_tasks block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ rake_tasks do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ require 'rake'
+ require 'rake/testtask'
+ require 'rdoc/task'
+
+ Rails.application.load_tasks
+ assert $ran_block
+ end
+
+ test "generators block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ generators do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_generators
+ assert $ran_block
+ end
+
+ test "console block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ console do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_console
+ assert $ran_block
+ end
+
+ test "runner block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ runner do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_runner
+ assert $ran_block
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 3601a58f67..8e76bf27f3 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -216,8 +216,8 @@ module ApplicationTests
require "#{app_path}/config/environment"
orig_database_url = ENV.delete("DATABASE_URL")
orig_rails_env, Rails.env = Rails.env, 'development'
- database_url_db_name = File.join(app_path, "db/database_url_db.sqlite3")
- ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}"
+ database_url_db_name = "db/database_url_db.sqlite3"
+ ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}"
ActiveRecord::Base.establish_connection
assert ActiveRecord::Base.connection
assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database])
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index 5bfea599e0..f8d8a673ae 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -21,6 +21,12 @@ module ApplicationTests
assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same"
end
+ def test_inheriting_multiple_times_from_application
+ new_application_class = Class.new(Rails::Application)
+
+ assert_not_equal Rails.application.object_id, new_application_class.instance.object_id
+ end
+
def test_initialization_of_multiple_copies_of_same_application
application1 = AppTemplate::Application.new
application2 = AppTemplate::Application.new
@@ -116,6 +122,26 @@ module ApplicationTests
assert_equal 3, $run_count, "There should have been three initializers that incremented the count"
end
+ def test_consoles_run_on_different_applications_go_to_the_same_class
+ $run_count = 0
+ AppTemplate::Application.console { $run_count += 1 }
+ AppTemplate::Application.new.console { $run_count += 1 }
+
+ assert_equal 0, $run_count, "Without loading the consoles, the count should be 0"
+ Rails.application.load_console
+ assert_equal 2, $run_count, "There should have been two consoles that increment the count"
+ end
+
+ def test_generators_run_on_different_applications_go_to_the_same_class
+ $run_count = 0
+ AppTemplate::Application.generators { $run_count += 1 }
+ AppTemplate::Application.new.generators { $run_count += 1 }
+
+ assert_equal 0, $run_count, "Without loading the generators, the count should be 0"
+ Rails.application.load_generators
+ assert_equal 2, $run_count, "There should have been two generators that increment the count"
+ end
+
def test_runners_run_on_different_applications_go_to_the_same_class
$run_count = 0
AppTemplate::Application.runner { $run_count += 1 }
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 35d9c31c1e..15414db00f 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -17,66 +17,57 @@ module ApplicationTests
end
def database_url_db_name
- File.join(app_path, "db/database_url_db.sqlite3")
+ "db/database_url_db.sqlite3"
end
def set_database_url
- ENV['DATABASE_URL'] = File.join("sqlite3://:@localhost", database_url_db_name)
+ ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}"
# ensure it's using the DATABASE_URL
FileUtils.rm_rf("#{app_path}/config/database.yml")
end
- def expected
- @expected ||= {}
- end
-
- def db_create_and_drop
+ def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
output = `bundle exec rake db:create`
- assert_equal output, ""
- assert File.exist?(expected[:database])
- assert_equal expected[:database],
- ActiveRecord::Base.connection_config[:database]
+ assert_empty output
+ assert File.exist?(expected_database)
+ assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
output = `bundle exec rake db:drop`
- assert_equal output, ""
- assert !File.exist?(expected[:database])
+ assert_empty output
+ assert !File.exist?(expected_database)
end
end
test 'db:create and db:drop without database url' do
require "#{app_path}/config/environment"
- expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database']
- db_create_and_drop
- end
+ db_create_and_drop ActiveRecord::Base.configurations[Rails.env]['database']
+ end
test 'db:create and db:drop with database url' do
require "#{app_path}/config/environment"
set_database_url
- expected[:database] = database_url_db_name
- db_create_and_drop
+ db_create_and_drop database_url_db_name
end
- def db_migrate_and_status
+ def db_migrate_and_status(expected_database)
Dir.chdir(app_path) do
`rails generate model book title:string;
bundle exec rake db:migrate`
output = `bundle exec rake db:migrate:status`
- assert_match(%r{database:\s+\S*#{Regexp.escape(expected[:database])}}, output)
+ assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output)
assert_match(/up\s+\d{14}\s+Create books/, output)
end
end
test 'db:migrate and db:migrate:status without database_url' do
require "#{app_path}/config/environment"
- expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database']
- db_migrate_and_status
+ db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]['database']
end
test 'db:migrate and db:migrate:status with database_url' do
require "#{app_path}/config/environment"
set_database_url
- expected[:database] = database_url_db_name
- db_migrate_and_status
+ db_migrate_and_status database_url_db_name
end
def db_schema_dump
@@ -97,12 +88,11 @@ module ApplicationTests
db_schema_dump
end
- def db_fixtures_load
+ def db_fixtures_load(expected_database)
Dir.chdir(app_path) do
`rails generate model book title:string;
bundle exec rake db:migrate db:fixtures:load`
- assert_match(/#{expected[:database]}/,
- ActiveRecord::Base.connection_config[:database])
+ assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
assert_equal 2, Book.count
end
@@ -110,43 +100,50 @@ module ApplicationTests
test 'db:fixtures:load without database_url' do
require "#{app_path}/config/environment"
- expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database']
- db_fixtures_load
+ db_fixtures_load ActiveRecord::Base.configurations[Rails.env]['database']
end
test 'db:fixtures:load with database_url' do
require "#{app_path}/config/environment"
set_database_url
- expected[:database] = database_url_db_name
- db_fixtures_load
+ db_fixtures_load database_url_db_name
end
- def db_structure_dump_and_load
+ def db_structure_dump_and_load(expected_database)
Dir.chdir(app_path) do
`rails generate model book title:string;
bundle exec rake db:migrate db:structure:dump`
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"books\"/, structure_dump)
`bundle exec rake environment db:drop db:structure:load`
- assert_match(/#{expected[:database]}/,
- ActiveRecord::Base.connection_config[:database])
+ assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
#if structure is not loaded correctly, exception would be raised
- assert Book.count, 0
+ assert_equal 0, Book.count
end
end
test 'db:structure:dump and db:structure:load without database_url' do
require "#{app_path}/config/environment"
- expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database']
- db_structure_dump_and_load
+ db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]['database']
end
test 'db:structure:dump and db:structure:load with database_url' do
require "#{app_path}/config/environment"
set_database_url
- expected[:database] = database_url_db_name
- db_structure_dump_and_load
+ db_structure_dump_and_load database_url_db_name
+ end
+
+ test 'db:structure:dump does not dump schema information when no migrations are used' do
+ Dir.chdir(app_path) do
+ # create table without migrations
+ `bundle exec rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+
+ stderr_output = capture(:stderr) { `bundle exec rake db:structure:dump` }
+ assert_empty stderr_output
+ structure_dump = File.read("db/structure.sql")
+ assert_match(/CREATE TABLE \"posts\"/, structure_dump)
+ end
end
def db_test_load_structure
@@ -157,9 +154,9 @@ module ApplicationTests
ActiveRecord::Base.establish_connection :test
require "#{app_path}/app/models/book"
#if structure is not loaded correctly, exception would be raised
- assert Book.count, 0
- assert_match(/#{ActiveRecord::Base.configurations['test']['database']}/,
- ActiveRecord::Base.connection_config[:database])
+ assert_equal 0, Book.count
+ assert_match ActiveRecord::Base.configurations['test']['database'],
+ ActiveRecord::Base.connection_config[:database]
end
end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 33c753868c..b7fd5d02c5 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -153,6 +153,37 @@ module ApplicationTests
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
end
end
+
+ test 'schema generation when dump_schema_after_migration is set' do
+ add_to_config('config.active_record.dump_schema_after_migration = false')
+
+ Dir.chdir(app_path) do
+ `rails generate model book title:string;
+ bundle exec rake db:migrate`
+
+ assert !File.exist?("db/schema.rb")
+ end
+
+ add_to_config('config.active_record.dump_schema_after_migration = true')
+
+ Dir.chdir(app_path) do
+ `rails generate model author name:string;
+ bundle exec rake db:migrate`
+
+ structure_dump = File.read("db/schema.rb")
+ assert_match(/create_table "authors"/, structure_dump)
+ end
+ end
+
+ test 'default schema generation after migration' do
+ Dir.chdir(app_path) do
+ `rails generate model book title:string;
+ bundle exec rake db:migrate`
+
+ structure_dump = File.read("db/schema.rb")
+ assert_match(/create_table "books"/, structure_dump)
+ end
+ end
end
end
end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 05f6338b68..95087bf29f 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -1,4 +1,5 @@
require "isolation/abstract_unit"
+require 'rails/source_annotation_extractor'
module ApplicationTests
module RakeTests
@@ -18,48 +19,27 @@ module ApplicationTests
test 'notes finds notes for certain file_types' do
app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>"
- app_file "app/views/home/index.html.haml", "-# TODO: note in haml"
- app_file "app/views/home/index.html.slim", "/ TODO: note in slim"
- app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee"
app_file "app/assets/javascripts/application.js", "// TODO: note in js"
app_file "app/assets/stylesheets/application.css", "// TODO: note in css"
- app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
- app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
- app_file "app/assets/stylesheets/application.css.less", "// TODO: note in less"
app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
app_file "lib/tasks/task.rake", "# TODO: note in rake"
app_file 'app/views/home/index.html.builder', '# TODO: note in builder'
+ app_file 'config/locales/en.yml', '# TODO: note in yml'
+ app_file 'config/locales/en.yaml', '# TODO: note in yaml'
+ app_file "app/views/home/index.ruby", "# TODO: note in ruby"
- boot_rails
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
-
- Rails.application.load_tasks
-
- Dir.chdir(app_path) do
- output = `bundle exec rake notes`
- lines = output.scan(/\[([0-9\s]+)\](\s)/)
-
+ run_rake_notes do |output, lines|
assert_match(/note in erb/, output)
- assert_match(/note in haml/, output)
- assert_match(/note in slim/, output)
- assert_match(/note in ruby/, output)
- assert_match(/note in coffee/, output)
assert_match(/note in js/, output)
assert_match(/note in css/, output)
- assert_match(/note in scss/, output)
- assert_match(/note in sass/, output)
- assert_match(/note in less/, output)
assert_match(/note in rake/, output)
assert_match(/note in builder/, output)
+ assert_match(/note in yml/, output)
+ assert_match(/note in yaml/, output)
+ assert_match(/note in ruby/, output)
- assert_equal 12, lines.size
-
- lines.each do |line|
- assert_equal 4, line[0].size
- assert_equal ' ', line[1]
- end
+ assert_equal 9, lines.size
+ assert_equal [4], lines.map(&:size).uniq
end
end
@@ -72,18 +52,7 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- boot_rails
-
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
-
- Rails.application.load_tasks
-
- Dir.chdir(app_path) do
- output = `bundle exec rake notes`
- lines = output.scan(/\[([0-9\s]+)\]/).flatten
-
+ run_rake_notes do |output, lines|
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
assert_match(/note in db directory/, output)
@@ -92,10 +61,7 @@ module ApplicationTests
assert_no_match(/note in some_other directory/, output)
assert_equal 5, lines.size
-
- lines.each do |line_number|
- assert_equal 4, line_number.size
- end
+ assert_equal [4], lines.map(&:size).uniq
end
end
@@ -108,18 +74,7 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- boot_rails
-
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
-
- Rails.application.load_tasks
-
- Dir.chdir(app_path) do
- output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
- lines = output.scan(/\[([0-9\s]+)\]/).flatten
-
+ run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes" do |output, lines|
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
assert_match(/note in db directory/, output)
@@ -129,10 +84,7 @@ module ApplicationTests
assert_match(/note in some_other directory/, output)
assert_equal 6, lines.size
-
- lines.each do |line_number|
- assert_equal 4, line_number.size
- end
+ assert_equal [4], lines.map(&:size).uniq
end
end
@@ -150,32 +102,51 @@ module ApplicationTests
end
EOS
- boot_rails
-
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
-
- Rails.application.load_tasks
-
- Dir.chdir(app_path) do
- output = `bundle exec rake notes_custom`
- lines = output.scan(/\[([0-9\s]+)\]/).flatten
-
+ run_rake_notes "bundle exec rake notes_custom" do |output, lines|
assert_match(/\[FIXME\] note in lib directory/, output)
assert_match(/\[TODO\] note in test directory/, output)
assert_no_match(/OPTIMIZE/, output)
assert_no_match(/note in app directory/, output)
assert_equal 2, lines.size
+ assert_equal [4], lines.map(&:size).uniq
+ end
+ end
- lines.each do |line_number|
- assert_equal 4, line_number.size
- end
+ test 'register a new extension' do
+ add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } }
+ app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
+ app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
+
+ run_rake_notes do |output, lines|
+ assert_match(/note in scss/, output)
+ assert_match(/note in sass/, output)
+ assert_equal 2, lines.size
end
end
private
+
+ def run_rake_notes(command = 'bundle exec rake notes')
+ boot_rails
+ load_tasks
+
+ Dir.chdir(app_path) do
+ output = `#{command}`
+ lines = output.scan(/\[([0-9\s]+)\]\s/).flatten
+
+ yield output, lines
+ end
+ end
+
+ def load_tasks
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+ end
+
def boot_rails
super
require "#{app_path}/config/environment"
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 317e73245c..e8c8de9f73 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -271,5 +271,16 @@ module ApplicationTests
end
end
end
+
+ def test_template_load_initializers
+ app_file "config/initializers/dummy.rb", "puts 'Hello, World!'"
+ app_file "template.rb", ""
+
+ output = Dir.chdir(app_path) do
+ `bundle exec rake rails:template LOCATION=template.rb`
+ end
+
+ assert_match(/Hello, World!/, output)
+ end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index a34beaedb3..1273f9d4c2 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -19,14 +19,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert console.sandbox?
end
- def test_debugger_option
- console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- assert console.debugger?
- end
-
def test_no_options
console = Rails::Console.new(app, parse_arguments([]))
- assert !console.debugger?
assert !console.sandbox?
end
@@ -36,13 +30,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/Loading \w+ environment \(Rails/, output)
end
- def test_start_with_debugger
- rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- rails_console.expects(:require_debugger).returns(nil)
-
- silence_stream(STDOUT) { rails_console.start }
- end
-
def test_start_with_sandbox
app.expects(:sandbox=).with(true)
FakeConsole.expects(:start)
@@ -52,6 +39,25 @@ 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
+ rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
+ rails_console.expects(:require_debugger).returns(nil)
+
+ 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/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 8246e76607..007dd886da 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -58,8 +58,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_assets
run_generator
- assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application", media: "all", "data-turbolinks-track" => true/)
- assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+"application", "data-turbolinks-track" => true/)
+ assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track' => true/)
+ assert_file("app/views/layouts/application.html.erb", /javascript_include_tag\s+'application', 'data-turbolinks-track' => true/)
assert_file("app/assets/stylesheets/application.css")
assert_file("app/assets/javascripts/application.js")
end
@@ -163,73 +163,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_arbitrary_code
- output = Tempfile.open('my_template') do |template|
- template.puts 'puts "You are using Rails version #{Rails::VERSION::STRING}."'
- template.close
- run_generator([destination_root, "-m", template.path])
- end
- assert_match 'You are using', output
- end
-
- def test_add_gemfile_entry
- Tempfile.open('my_template') do |template|
- template.puts 'gemfile_entry "tenderlove"'
- template.flush
- template.close
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile", /tenderlove/
- end
- end
-
- def test_add_skip_entry
- Tempfile.open 'my_template' do |template|
- template.puts 'add_gem_entry_filter { |gem| gem.name != "jbuilder" }'
- template.close
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'jbuilder', contents
- end
- end
- end
-
- def test_remove_gem
- Tempfile.open 'my_template' do |template|
- template.puts 'remove_gem "jbuilder"'
- template.close
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'jbuilder', contents
- end
- end
- end
-
- def test_skip_turbolinks_when_it_is_not_on_gemfile
- Tempfile.open 'my_template' do |template|
- template.puts 'add_gem_entry_filter { |gem| gem.name != "turbolinks" }'
- template.flush
-
- run_generator([destination_root, "-n", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'turbolinks', contents
- end
-
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match 'turbolinks', contents
- end
-
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match('data-turbolinks-track', contents)
- end
-
- assert_file "app/assets/javascripts/application.js" do |contents|
- assert_no_match 'turbolinks', contents
- end
- end
- end
-
def test_config_another_database
run_generator([destination_root, "-d", "mysql"])
assert_file "config/database.yml", /mysql/
@@ -304,6 +237,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
+ assert_no_file "config/initializers/assets.rb"
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
end
@@ -319,7 +253,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.assets\.digest = true/, content)
assert_no_match(/config\.assets\.js_compressor = :uglifier/, content)
assert_no_match(/config\.assets\.css_compressor = :sass/, content)
- assert_no_match(/config\.assets\.version = '1\.0'/, content)
end
end
@@ -332,13 +265,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_inclusion_of_plateform_dependent_gems
- run_generator([destination_root])
- if RUBY_ENGINE == 'rbx'
- assert_gem 'rubysl'
- end
- end
-
def test_jquery_is_the_default_javascript_library
run_generator
assert_file "app/assets/javascripts/application.js" do |contents|
@@ -364,8 +290,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "vendor/assets/javascripts"
assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_match(/stylesheet_link_tag\s+"application", media: "all" %>/, contents)
- assert_no_match(/javascript_include_tag\s+"application" \%>/, contents)
+ assert_match(/stylesheet_link_tag\s+'application', media: 'all' %>/, contents)
+ assert_no_match(/javascript_include_tag\s+'application' \%>/, contents)
end
assert_file "Gemfile" do |content|
@@ -379,14 +305,17 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /gem 'jbuilder'/
end
- def test_inclusion_of_debugger
+ def test_inclusion_of_a_debugger
run_generator
if defined?(JRUBY_VERSION)
assert_file "Gemfile" do |content|
+ assert_no_match(/byebug/, content)
assert_no_match(/debugger/, content)
end
- else
+ elsif RUBY_VERSION < '2.0.0'
assert_file "Gemfile", /# gem 'debugger'/
+ else
+ assert_file "Gemfile", /# gem 'byebug'/
end
end
@@ -482,7 +411,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
-protected
+ def test_gitignore_when_sqlite3
+ run_generator
+
+ assert_file '.gitignore' do |content|
+ assert_match(/sqlite3/, content)
+ end
+ end
+
+ def test_gitignore_when_no_active_record
+ run_generator [destination_root, '--skip-active-record']
+
+ assert_file '.gitignore' do |content|
+ assert_no_match(/sqlite/i, content)
+ end
+ end
+
+ def test_gitignore_when_non_sqlite3_db
+ run_generator([destination_root, "-d", "mysql"])
+
+ assert_file '.gitignore' do |content|
+ assert_no_match(/sqlite/i, content)
+ end
+ end
+
+ protected
def action(*args, &block)
silence(:stdout) { generator.send(*args, &block) }
diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb
new file mode 100644
index 0000000000..e16a77479a
--- /dev/null
+++ b/railties/test/generators/create_migration_test.rb
@@ -0,0 +1,134 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/migration/migration_generator'
+
+class CreateMigrationTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
+ class Migrator < Rails::Generators::MigrationGenerator
+ include Rails::Generators::Migration
+
+ def self.next_migration_number(dirname)
+ current_migration_number(dirname) + 1
+ end
+ end
+
+ tests Migrator
+
+ def default_destination_path
+ "db/migrate/create_articles.rb"
+ end
+
+ def create_migration(destination_path = default_destination_path, config = {}, generator_options = {}, &block)
+ migration_name = File.basename(destination_path, '.rb')
+ generator([migration_name], generator_options)
+ generator.set_migration_assigns!(destination_path)
+
+ dir, base = File.split(destination_path)
+ timestamped_destination_path = File.join(dir, ["%migration_number%", base].join('_'))
+
+ @migration = Rails::Generators::Actions::CreateMigration.new(generator, timestamped_destination_path, block || "contents", config)
+ end
+
+ def migration_exists!(*args)
+ @existing_migration = create_migration(*args)
+ invoke!
+ @generator = nil
+ end
+
+ def invoke!
+ capture(:stdout) { @migration.invoke! }
+ end
+
+ def revoke!
+ capture(:stdout) { @migration.revoke! }
+ end
+
+ def test_invoke
+ create_migration
+
+ assert_match(/create db\/migrate\/1_create_articles.rb\n/, invoke!)
+ assert_file @migration.destination
+ end
+
+ def test_invoke_pretended
+ create_migration(default_destination_path, {}, { pretend: true })
+
+ assert_no_file @migration.destination
+ end
+
+ def test_invoke_when_exists
+ migration_exists!
+ create_migration
+
+ assert_equal @existing_migration.destination, @migration.existing_migration
+ end
+
+ def test_invoke_when_exists_identical
+ migration_exists!
+ create_migration
+
+ assert_match(/identical db\/migrate\/1_create_articles.rb\n/, invoke!)
+ assert @migration.identical?
+ end
+
+ def test_invoke_when_exists_not_identical
+ migration_exists!
+ create_migration { "different content" }
+
+ assert_raise(Rails::Generators::Error) { invoke! }
+ end
+
+ def test_invoke_forced_when_exists_not_identical
+ dest = "db/migrate/migration.rb"
+ migration_exists!(dest)
+ create_migration(dest, force: true) { "different content" }
+
+ stdout = invoke!
+ assert_match(/remove db\/migrate\/1_migration.rb\n/, stdout)
+ assert_match(/create db\/migrate\/2_migration.rb\n/, stdout)
+ assert_file @migration.destination
+ assert_no_file @existing_migration.destination
+ end
+
+ def test_invoke_forced_pretended_when_exists_not_identical
+ migration_exists!
+ create_migration(default_destination_path, { force: true }, { pretend: true }) do
+ "different content"
+ end
+
+ stdout = invoke!
+ assert_match(/remove db\/migrate\/1_create_articles.rb\n/, stdout)
+ assert_match(/create db\/migrate\/2_create_articles.rb\n/, stdout)
+ assert_no_file @migration.destination
+ end
+
+ def test_invoke_skipped_when_exists_not_identical
+ migration_exists!
+ create_migration(default_destination_path, {}, { skip: true }) { "different content" }
+
+ assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!)
+ assert_no_file @migration.destination
+ end
+
+ def test_revoke
+ migration_exists!
+ create_migration
+
+ assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ assert_no_file @existing_migration.destination
+ end
+
+ def test_revoke_pretended
+ migration_exists!
+ create_migration(default_destination_path, {}, { pretend: true })
+
+ assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ assert_file @existing_migration.destination
+ end
+
+ def test_revoke_when_no_exists
+ create_migration
+
+ assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ end
+end
diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb
index 94d2c1bf50..7871399dd7 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -1,7 +1,6 @@
require 'active_support/test_case'
require 'active_support/testing/autorun'
require 'rails/generators/app_base'
-require 'rails/generators/rails/app/app_generator'
module Rails
module Generators
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index d209801f60..25649881eb 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -69,12 +69,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_text_template_engine
run_generator
assert_file "app/views/notifier/foo.text.erb" do |view|
- assert_match(%r(app/views/notifier/foo\.text\.erb), view)
+ assert_match(%r(\sapp/views/notifier/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
assert_file "app/views/notifier/bar.text.erb" do |view|
- assert_match(%r(app/views/notifier/bar\.text\.erb), view)
+ assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
@@ -82,12 +82,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_invokes_default_html_template_engine
run_generator
assert_file "app/views/notifier/foo.html.erb" do |view|
- assert_match(%r(app/views/notifier/foo\.html\.erb), view)
+ assert_match(%r(\sapp/views/notifier/foo\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
assert_file "app/views/notifier/bar.html.erb" do |view|
- assert_match(%r(app/views/notifier/bar\.html\.erb), view)
+ assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index d876597944..6fac643ed0 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -197,4 +197,54 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
def test_properly_identifies_usage_file
assert generator_class.send(:usage_path)
end
+
+ def test_migration_with_singular_table_name
+ with_singular_table_name do
+ migration = "add_title_body_to_post"
+ run_generator [migration, 'title:string']
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/add_column :post, :title, :string/, change)
+ end
+ end
+ end
+ end
+
+ def test_create_join_table_migration_with_singular_table_name
+ with_singular_table_name do
+ migration = "add_media_join_table"
+ run_generator [migration, "artist_id", "music:uniq"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/create_join_table :artist, :music/, change)
+ assert_match(/# t.index \[:artist_id, :music_id\]/, change)
+ assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change)
+ end
+ end
+ end
+ end
+
+ def test_create_table_migration_with_singular_table_name
+ with_singular_table_name do
+ run_generator ["create_book", "title:string", "content:text"]
+ assert_migration "db/migrate/create_book.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/create_table :book/, change)
+ assert_match(/ t\.string :title/, change)
+ assert_match(/ t\.text :content/, change)
+ end
+ end
+ end
+ end
+
+ private
+
+ def with_singular_table_name
+ old_state = ActiveRecord::Base.pluralize_table_names
+ ActiveRecord::Base.pluralize_table_names = false
+ yield
+ ensure
+ ActiveRecord::Base.pluralize_table_names = old_state
+ end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 01ab77ee20..b67cf02d7b 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -34,6 +34,13 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_no_migration "db/migrate/create_accounts.rb"
end
+ def test_plural_names_are_singularized
+ content = run_generator ["accounts".freeze]
+ assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
+ assert_file "test/models/account_test.rb", /class AccountTest/
+ assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content)
+ end
+
def test_model_with_underscored_parent_option
run_generator ["account", "--parent", "admin/account"]
assert_file "app/models/account.rb", /class Account < Admin::Account/
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 932cd75bcb..853af80111 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -64,14 +64,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
- def test_inclusion_of_debugger
+ def test_inclusion_of_a_debugger
run_generator [destination_root, '--full']
if defined?(JRUBY_VERSION)
assert_file "Gemfile" do |content|
+ assert_no_match(/byebug/, content)
assert_no_match(/debugger/, content)
end
- else
+ elsif RUBY_VERSION < '2.0.0'
assert_file "Gemfile", /# gem 'debugger'/
+ else
+ assert_file "Gemfile", /# gem 'byebug'/
end
end
@@ -156,7 +159,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_match(/bukkits/, contents)
end
assert_match(/run bundle install/, result)
- assert_match(/Using bukkits \(0\.0\.1\)/, result)
+ assert_match(/Using bukkits \(?0\.0\.1\)?/, result)
assert_match(/Your bundle is complete/, result)
assert_equal 1, result.scan("Your bundle is complete").size
end
@@ -185,22 +188,22 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator
FileUtils.cd destination_root
quietly { system 'bundle install' }
- assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
+ assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
end
def test_ensure_that_tests_works_in_full_mode
run_generator [destination_root, "--full", "--skip_active_record"]
FileUtils.cd destination_root
quietly { system 'bundle install' }
- assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`)
+ assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
end
def test_ensure_that_migration_tasks_work_with_mountable_option
run_generator [destination_root, "--mountable"]
FileUtils.cd destination_root
quietly { system 'bundle install' }
- `bundle exec rake db:migrate`
- assert_equal 0, $?.exitstatus
+ output = `bundle exec rake db:migrate 2>&1`
+ assert $?.success?, "Command failed: #{output}"
end
def test_creating_engine_in_full_mode
@@ -355,6 +358,18 @@ class PluginGeneratorTest < Rails::Generators::TestCase
FileUtils.rm gemfile_path
end
+ def test_generating_controller_inside_mountable_engine
+ run_generator [destination_root, "--mountable"]
+
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g controller admin/dashboard foo`
+ end
+
+ assert_file "config/routes.rb" do |contents|
+ assert_match(/namespace :admin/, contents)
+ assert_no_match(/namespace :bukkit/, contents)
+ end
+ end
protected
def action(*args, &block)
diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb
index 3d4e694361..dcdff22152 100644
--- a/railties/test/generators/resource_generator_test.rb
+++ b/railties/test/generators/resource_generator_test.rb
@@ -63,19 +63,19 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
content = run_generator ["accounts".freeze]
assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
assert_file "test/models/account_test.rb", /class AccountTest/
- assert_match(/Plural version of the model detected, using singularized version. Override with --force-plural./, content)
+ assert_match(/\[WARNING\] The model name 'accounts' was recognized as a plural, using the singular 'account' instead\. Override with --force-plural or setup custom inflection rules for this noun before running the generator\./, content)
end
def test_plural_names_can_be_forced
content = run_generator ["accounts", "--force-plural"]
assert_file "app/models/accounts.rb", /class Accounts < ActiveRecord::Base/
assert_file "test/models/accounts_test.rb", /class AccountsTest/
- assert_no_match(/Plural version of the model detected/, content)
+ assert_no_match(/\[WARNING\]/, content)
end
def test_mass_nouns_do_not_throw_warnings
content = run_generator ["sheep".freeze]
- assert_no_match(/Plural version of the model detected/, content)
+ assert_no_match(/\[WARNING\]/, content)
end
def test_route_is_removed_on_revoke
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 26e56a162c..3c1123b53d 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -160,13 +160,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
Unknown::Generators.send :remove_const, :ActiveModel
end
- def test_new_hash_style
- run_generator
- assert_file "app/controllers/users_controller.rb" do |content|
- assert_match(/render action: 'new'/, content)
- end
- end
-
def test_model_name_option
run_generator ["Admin::User", "--model-name=User"]
assert_file "app/controllers/admin/users_controller.rb" do |content|
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 4cbd4822be..a458240d2f 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -72,6 +72,14 @@ module RailtiesTest
assert $to_prepare
end
+ test "railtie have access to application in before_configuration callbacks" do
+ $after_initialize = false
+ class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = Rails.root.to_path } ; end
+ assert_not $before_configuration
+ require "#{app_path}/config/environment"
+ assert_equal app_path, $before_configuration
+ end
+
test "railtie can add after_initialize callbacks" do
$after_initialize = false
class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end
diff --git a/railties/test/version_test.rb b/railties/test/version_test.rb
new file mode 100644
index 0000000000..f270d8f0c9
--- /dev/null
+++ b/railties/test/version_test.rb
@@ -0,0 +1,12 @@
+require 'abstract_unit'
+
+class VersionTest < ActiveSupport::TestCase
+ def test_rails_version_returns_a_string
+ assert Rails.version.is_a? String
+ end
+
+ def test_rails_gem_version_returns_a_correct_gem_version_object
+ assert Rails.gem_version.is_a? Gem::Version
+ assert_equal Rails.version, Rails.gem_version.to_s
+ end
+end
diff --git a/tasks/release.rb b/tasks/release.rb
index 439a9e0c05..767feaf236 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -15,38 +15,37 @@ directory "pkg"
rm_f gem
end
- task :update_version_rb do
+ task :update_versions do
glob = root.dup
- glob << "/#{framework}/lib/*" unless framework == "rails"
- glob << "/version.rb"
+ if framework == "rails"
+ glob << "/version.rb"
+ else
+ glob << "/#{framework}/lib/*"
+ glob << "/gem_version.rb"
+ end
file = Dir[glob].first
ruby = File.read(file)
- if framework == "rails" || framework == "railties"
- major, minor, tiny, pre = version.split('.')
- pre = pre ? pre.inspect : "nil"
+ major, minor, tiny, pre = version.split('.')
+ pre = pre ? pre.inspect : "nil"
- ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}")
- raise "Could not insert MAJOR in #{file}" unless $1
+ ruby.gsub!(/^(\s*)MAJOR(\s*)= .*?$/, "\\1MAJOR = #{major}")
+ raise "Could not insert MAJOR in #{file}" unless $1
- ruby.gsub!(/^(\s*)MINOR(\s*)= .*?$/, "\\1MINOR = #{minor}")
- raise "Could not insert MINOR in #{file}" unless $1
+ ruby.gsub!(/^(\s*)MINOR(\s*)= .*?$/, "\\1MINOR = #{minor}")
+ raise "Could not insert MINOR in #{file}" unless $1
- ruby.gsub!(/^(\s*)TINY(\s*)= .*?$/, "\\1TINY = #{tiny}")
- raise "Could not insert TINY in #{file}" unless $1
+ ruby.gsub!(/^(\s*)TINY(\s*)= .*?$/, "\\1TINY = #{tiny}")
+ raise "Could not insert TINY in #{file}" unless $1
- ruby.gsub!(/^(\s*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}")
- raise "Could not insert PRE in #{file}" unless $1
- else
- ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
- raise "Could not insert Gem::Version in #{file}" unless $1
- end
+ ruby.gsub!(/^(\s*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}")
+ raise "Could not insert PRE in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
end
- task gem => %w(update_version_rb pkg) do
+ task gem => %w(update_versions pkg) do
cmd = ""
cmd << "cd #{framework} && " unless framework == "rails"
cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/"
@@ -68,7 +67,7 @@ end
namespace :changelog do
task :release_date do
- FRAMEWORKS.each do |fw|
+ (FRAMEWORKS + ['guides']).each do |fw|
require 'date'
replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')'
fname = File.join fw, 'CHANGELOG.md'
@@ -79,7 +78,7 @@ namespace :changelog do
end
task :release_summary do
- FRAMEWORKS.each do |fw|
+ (FRAMEWORKS + ['guides']).each do |fw|
puts "## #{fw}"
fname = File.join fw, 'CHANGELOG.md'
contents = File.readlines fname
@@ -93,16 +92,17 @@ namespace :changelog do
end
namespace :all do
- task :build => FRAMEWORKS.map { |f| "#{f}:build" } + ['rails:build']
- task :install => FRAMEWORKS.map { |f| "#{f}:install" } + ['rails:install']
- task :push => FRAMEWORKS.map { |f| "#{f}:push" } + ['rails:push']
+ task :build => FRAMEWORKS.map { |f| "#{f}:build" } + ['rails:build']
+ task :update_versions => FRAMEWORKS.map { |f| "#{f}:update_versions" } + ['rails:update_versions']
+ task :install => FRAMEWORKS.map { |f| "#{f}:install" } + ['rails:install']
+ task :push => FRAMEWORKS.map { |f| "#{f}:push" } + ['rails:push']
task :ensure_clean_state do
unless `git status -s | grep -v RAILS_VERSION`.strip.empty?
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 923cab4e2a..c7397c4f15 100644
--- a/version.rb
+++ b/version.rb
@@ -1,9 +1,14 @@
module Rails
+ # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
module VERSION
MAJOR = 4
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end