aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVijay Dev <vijaydev.cse@gmail.com>2013-03-30 15:47:38 +0530
committerVijay Dev <vijaydev.cse@gmail.com>2013-03-30 15:47:38 +0530
commite63086c135bf17a43e0807ecdef3e54b22048152 (patch)
tree89f51923c9ea02d49f4ea97e8ec3dc0f5e3c5119
parent06e22c01e86e47ebe0835e4c6ec351175cf91b96 (diff)
parent6d8c070821bc846eb263b8c045ae652ebd751569 (diff)
downloadrails-e63086c135bf17a43e0807ecdef3e54b22048152.tar.gz
rails-e63086c135bf17a43e0807ecdef3e54b22048152.tar.bz2
rails-e63086c135bf17a43e0807ecdef3e54b22048152.zip
Merge branch 'master' of github.com:rails/rails
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml2
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--Gemfile18
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--Rakefile93
-rw-r--r--actionmailer/CHANGELOG.md4
-rw-r--r--actionmailer/lib/action_mailer/version.rb13
-rw-r--r--actionmailer/test/mailers/base_mailer.rb2
-rw-r--r--actionpack/CHANGELOG.md87
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb14
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb5
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb1
-rw-r--r--actionpack/lib/action_controller/metal/live.rb4
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb8
-rw-r--r--actionpack/lib/action_controller/test_case.rb36
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb54
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb41
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb196
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb71
-rw-r--r--actionpack/lib/action_pack/version.rb13
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionpack/lib/action_view/lookup_context.rb8
-rw-r--r--actionpack/lib/action_view/template.rb3
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb10
-rw-r--r--actionpack/test/abstract/callbacks_test.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract/layouts_test.rb16
-rw-r--r--actionpack/test/controller/base_test.rb12
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb8
-rw-r--r--actionpack/test/controller/integration_test.rb15
-rw-r--r--actionpack/test/controller/layout_test.rb24
-rw-r--r--actionpack/test/controller/live_stream_test.rb15
-rw-r--r--actionpack/test/controller/localized_templates_test.rb9
-rw-r--r--actionpack/test/controller/output_escaping_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb11
-rw-r--r--actionpack/test/controller/test_case_test.rb22
-rw-r--r--actionpack/test/dispatch/cookies_test.rb65
-rw-r--r--actionpack/test/dispatch/header_test.rb128
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb21
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb37
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb8
-rw-r--r--actionpack/test/fixtures/multipart/bracketed_utf8_param5
-rw-r--r--actionpack/test/fixtures/multipart/single_utf8_param5
-rw-r--r--actionpack/test/template/form_helper_test.rb4
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb15
-rw-r--r--actionpack/test/template/url_helper_test.rb1
-rw-r--r--activemodel/CHANGELOG.md30
-rw-r--r--activemodel/lib/active_model/errors.rb14
-rw-r--r--activemodel/lib/active_model/secure_password.rb9
-rw-r--r--activemodel/lib/active_model/validations.rb48
-rw-r--r--activemodel/lib/active_model/version.rb13
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb2
-rw-r--r--activemodel/test/cases/callbacks_test.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb21
-rw-r--r--activemodel/test/cases/naming_test.rb2
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb8
-rw-r--r--activerecord/CHANGELOG.md241
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/examples/associations.pngbin40623 -> 0 bytes
-rw-r--r--activerecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/associations/association.rb6
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb22
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb42
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb14
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb113
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb409
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb81
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
-rw-r--r--activerecord/lib/active_record/core.rb37
-rw-r--r--activerecord/lib/active_record/counter_cache.rb3
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb18
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb11
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb20
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/schema_migration.rb13
-rw-r--r--activerecord/lib/active_record/scoping/default.rb8
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb10
-rw-r--r--activerecord/lib/active_record/version.rb13
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb5
-rw-r--r--activerecord/test/cases/adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb53
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb13
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb31
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb25
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb6
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb7
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb4
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb120
-rw-r--r--activerecord/test/cases/batches_test.rb6
-rw-r--r--activerecord/test/cases/calculations_test.rb21
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb13
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb41
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/counter_cache_test.rb18
-rw-r--r--activerecord/test/cases/dup_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb14
-rw-r--r--activerecord/test/cases/inheritance_test.rb15
-rw-r--r--activerecord/test/cases/locking_test.rb11
-rw-r--r--activerecord/test/cases/migration/columns_test.rb13
-rw-r--r--activerecord/test/cases/named_scope_test.rb13
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb45
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb25
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/relation_test.rb12
-rw-r--r--activerecord/test/cases/relations_test.rb24
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb16
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb20
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb8
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb10
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb4
-rw-r--r--activerecord/test/fixtures/dog_lovers.yml3
-rw-r--r--activerecord/test/fixtures/dogs.yml1
-rw-r--r--activerecord/test/fixtures/friendships.yml4
-rw-r--r--activerecord/test/fixtures/people.yml3
-rw-r--r--activerecord/test/models/author.rb8
-rw-r--r--activerecord/test/models/book.rb2
-rw-r--r--activerecord/test/models/dog.rb5
-rw-r--r--activerecord/test/models/dog_lover.rb5
-rw-r--r--activerecord/test/models/friendship.rb4
-rw-r--r--activerecord/test/models/liquid.rb3
-rw-r--r--activerecord/test/models/person.rb5
-rw-r--r--activerecord/test/models/project.rb8
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb10
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb10
-rw-r--r--activerecord/test/schema/schema.rb12
-rw-r--r--activesupport/CHANGELOG.md13
-rw-r--r--activesupport/Rakefile2
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/cache.rb6
-rw-r--r--activesupport/lib/active_support/core_ext.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb54
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb2
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb5
-rw-r--r--activesupport/lib/active_support/version.rb13
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb6
-rw-r--r--activesupport/test/caching_test.rb4
-rw-r--r--activesupport/test/core_ext/marshal_test.rb4
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb26
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb8
-rw-r--r--activesupport/test/dependencies_test.rb6
-rw-r--r--activesupport/test/dependencies_test_helpers.rb (renamed from activesupport/test/dependecies_test_helpers.rb)2
-rw-r--r--activesupport/test/fixtures/xml/jdom_doctype.dtd1
-rw-r--r--activesupport/test/fixtures/xml/jdom_entities.txt1
-rw-r--r--activesupport/test/fixtures/xml/jdom_include.txt1
-rw-r--r--activesupport/test/inflector_test.rb4
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb8
-rw-r--r--activesupport/test/notifications_test.rb2
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb10
-rw-r--r--activesupport/test/time_zone_test.rb16
-rw-r--r--activesupport/test/transliterate_test.rb2
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb38
-rwxr-xr-xci/travis.rb4
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_posts.pngbin11780 -> 31314 bytes
-rw-r--r--guides/code/getting_started/Gemfile26
-rw-r--r--guides/code/getting_started/config/application.rb5
-rw-r--r--guides/code/getting_started/public/404.html26
-rw-r--r--guides/code/getting_started/public/422.html24
-rw-r--r--guides/code/getting_started/public/500.html26
-rw-r--r--guides/source/2_2_release_notes.md30
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md27
-rw-r--r--guides/source/active_record_querying.md11
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md27
-rw-r--r--guides/source/engines.md9
-rw-r--r--guides/source/form_helpers.md26
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/testing.md176
-rw-r--r--guides/source/upgrading_ruby_on_rails.md29
-rw-r--r--rails.gemspec4
-rw-r--r--railties/CHANGELOG.md92
-rw-r--r--railties/RDOC_MAIN.rdoc73
-rw-r--r--railties/lib/rails.rb4
-rw-r--r--railties/lib/rails/all.rb4
-rw-r--r--railties/lib/rails/api/task.rb147
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/commands.rb11
-rw-r--r--railties/lib/rails/commands/application.rb2
-rw-r--r--railties/lib/rails/commands/server.rb8
-rw-r--r--railties/lib/rails/commands/test_runner.rb146
-rw-r--r--railties/lib/rails/generators/app_base.rb84
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb9
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb4
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html24
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb1
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml16
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb6
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/tasks/documentation.rake48
-rw-r--r--railties/lib/rails/test_unit/testing.rake79
-rw-r--r--railties/lib/rails/version.rb13
-rw-r--r--railties/railties.gemspec3
-rw-r--r--railties/test/application/asset_debugging_test.rb2
-rw-r--r--railties/test/application/assets_test.rb6
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/application/console_test.rb16
-rw-r--r--railties/test/application/initializers/i18n_test.rb2
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/middleware/cookies_test.rb2
-rw-r--r--railties/test/application/rake_test.rb23
-rw-r--r--railties/test/application/test_runner_test.rb312
-rw-r--r--railties/test/generators/app_generator_test.rb7
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb4
-rw-r--r--railties/test/generators/scaffold_generator_test.rb43
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb2
-rw-r--r--railties/test/railties/railtie_test.rb2
-rw-r--r--tasks/release.rb16
-rw-r--r--version.rb12
306 files changed, 4285 insertions, 1613 deletions
diff --git a/.gitignore b/.gitignore
index a3a5304ecd..7236e395a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ debug.log
/.bundle
/.ruby-version
/Gemfile.lock
-/pkg
+pkg
/dist
/doc/rdoc
/*/doc
@@ -20,4 +20,3 @@ debug.log
/railties/doc
/railties/tmp
/guides/output
-/RDOC_MAIN.rdoc
diff --git a/.travis.yml b/.travis.yml
index ba5526c009..5214b989af 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,4 +23,4 @@ notifications:
on_failure: always
rooms:
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
-bundler_args: --path vendor/bundle
+bundler_args: --path vendor/bundle --without test
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adc5eb6914..6d3cf072c1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,14 @@
Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the team](http://contributors.rubyonrails.org)!
-Please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide before submitting any code.
+* If you want to submit a bug report please make sure to follow our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue).
+
+* If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide.
*We only accept bug reports and pull requests in GitHub*.
-If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
-If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
+* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
Thanks! :heart: :heart: :heart: <br />
Rails Team
diff --git a/Gemfile b/Gemfile
index d8b3390f87..9abc8a5d66 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,9 +5,10 @@ gemspec
gem 'mocha', '~> 0.13.0', require: false
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.0.0'
-gem 'jquery-rails', '~> 2.2.0'
+gem 'jquery-rails', github: 'rails/jquery-rails'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0.beta1'
+gem 'thor', github: 'wycats/thor'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
@@ -27,11 +28,16 @@ gem 'dalli', '>= 2.2.1'
local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
instance_eval File.read local_gemfile if File.exists? local_gemfile
-platforms :mri do
- group :test do
- gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
- gem 'debugger' if !ENV['TRAVIS']
+group :test do
+ platforms :mri_19 do
+ gem 'ruby-prof', '~> 0.11.2'
end
+
+ platforms :mri_19, :mri_20 do
+ gem 'debugger'
+ end
+
+ gem 'benchmark-ips'
end
platforms :ruby do
@@ -76,5 +82,3 @@ end
# A gem necessary for ActiveRecord tests with IBM DB
gem 'ibm_db' if ENV['IBM_DB']
-
-gem 'benchmark-ips'
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index b065be4922..6f8c79eef2 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -13,7 +13,7 @@ Today is mostly coordination tasks. Here are the things you must do today:
Do not release with a Red CI. You can find the CI status here:
- http://travis-ci.org/#!/rails/rails
+ http://travis-ci.org/rails/rails
=== Is Sam Ruby happy? If not, make him happy.
diff --git a/Rakefile b/Rakefile
index 490627d22c..177765f348 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,9 +1,9 @@
-require 'rdoc/task'
require 'sdoc'
require 'net/http'
$:.unshift File.expand_path('..', __FILE__)
require "tasks/release"
+require 'railties/lib/rails/api/task'
desc "Build gem files for all projects"
task :build => "all:build"
@@ -47,101 +47,14 @@ task :install => :gem do
end
desc "Generate documentation for the Rails framework"
-RDoc::Task.new do |rdoc|
- RDOC_MAIN = 'RDOC_MAIN.rdoc'
-
- # This is a hack.
- #
- # Backslashes are needed to prevent RDoc from autolinking "Rails" to the
- # documentation of the Rails module. On the other hand, as of this
- # writing README.rdoc is displayed in the front page of the project in
- # GitHub, where backslashes are shown and look weird.
- #
- # The temporary solution is to have a README.rdoc without backslashes for
- # GitHub, and gsub it to generate the main page of the API.
- #
- # Also, relative links in GitHub have to point to blobs, whereas in the API
- # they need to point to files.
- #
- # The idea for the future is to have totally different files, since the
- # API is no longer a generic entry point to Rails and deserves a
- # dedicated main page specifically thought as an API entry point.
- rdoc.before_running_rdoc do
- rdoc_main = File.read('README.rdoc')
-
- # The ^(?=\S) assertion prevents code blocks from being processed,
- # since no autolinking happens there and RDoc displays the backslash
- # otherwise.
- rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
- rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
-
- # Remove Travis and Gemnasium status images from API pages. Only the GitHub
- # README page gets these images. Travis's HTTPS build image is used to
- # avoid GitHub caching: http://about.travis-ci.org/docs/user/status-images
- rdoc_main.gsub!(/^== Code Status(\n(?!==).*)*/, '')
-
- File.open(RDOC_MAIN, 'w') do |f|
- f.write(rdoc_main)
- end
-
- rdoc.rdoc_files.include(RDOC_MAIN)
- end
-
- rdoc.rdoc_dir = 'doc/rdoc'
- rdoc.title = "Ruby on Rails Documentation"
-
- rdoc.options << '-f' << 'sdoc'
- rdoc.options << '-T' << 'rails'
- rdoc.options << '-e' << 'UTF-8'
- rdoc.options << '-g' # SDoc flag, link methods to GitHub
- rdoc.options << '-m' << RDOC_MAIN
-
- rdoc.rdoc_files.include('railties/CHANGELOG.md')
- rdoc.rdoc_files.include('railties/MIT-LICENSE')
- rdoc.rdoc_files.include('railties/README.rdoc')
- rdoc.rdoc_files.include('railties/lib/**/*.rb')
- rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb')
-
- rdoc.rdoc_files.include('activerecord/README.rdoc')
- rdoc.rdoc_files.include('activerecord/CHANGELOG.md')
- rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
- rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
-
- rdoc.rdoc_files.include('actionpack/README.rdoc')
- rdoc.rdoc_files.include('actionpack/CHANGELOG.md')
- rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb')
- rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
- rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
-
- rdoc.rdoc_files.include('actionmailer/README.rdoc')
- rdoc.rdoc_files.include('actionmailer/CHANGELOG.md')
- rdoc.rdoc_files.include('actionmailer/lib/action_mailer/**/*.rb')
- rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
-
- rdoc.rdoc_files.include('activesupport/README.rdoc')
- rdoc.rdoc_files.include('activesupport/CHANGELOG.md')
- rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
- rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
-
- rdoc.rdoc_files.include('activemodel/README.rdoc')
- rdoc.rdoc_files.include('activemodel/CHANGELOG.md')
- rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb')
-end
-
-# Enhance rdoc task to copy referenced images also
-task :rdoc do
- FileUtils.mkdir_p "doc/rdoc/files/examples/"
- FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png"
-end
+Rails::API::RepoTask.new('rdoc')
desc 'Bump all versions to match version.rb'
task :update_versions do
require File.dirname(__FILE__) + "/version"
File.open("RAILS_VERSION", "w") do |f|
- f.write Rails::VERSION::STRING + "\n"
+ f.puts Rails.version
end
constants = {
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 8f74ac0928..487e57be7b 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -21,7 +21,7 @@
*Olek Janiszewski*
* Eager loading made to use relation's `in_clause_length` instead of host's one.
- Fix #8474
+ Fixes #8474.
*Boris Staal*
@@ -29,7 +29,7 @@
*Nate Berkopec*
* Do not render views when mail() isn't called.
- Fix #7761
+ Fixes #7761.
*Yves Senn*
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 997046b971..89e31c4be6 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,10 +1,11 @@
module ActionMailer
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActionMailer as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionMailer.version.segments
+ STRING = ActionMailer.version.to_s
end
end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 504ca36483..20b6671283 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -119,7 +119,7 @@ class BaseMailer < ActionMailer::Base
def without_mail_call
end
- def with_nil_as_return_value(hash = {})
+ def with_nil_as_return_value
mail(:template_name => "welcome")
nil
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f757911c23..586b15a62d 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,69 @@
## Rails 4.0.0 (unreleased) ##
+* Ensure consistent fallback to the default layout lookup for layouts set
+ using symbols or procs that return nil.
+
+ All of the following layouts will result in the default layout lookup:
+
+ layout nil
+
+ layout proc { nil }
+
+ layout :returns_nil
+
+ def returns_nil
+ nil
+ end
+
+ Previously symbols and procs which returned nil resulted in no layout which
+ differed from the `layout nil` behavior.
+
+ *Chris Nicola*
+
+* Create `UpgradeLegacySignedCookieJar` to transparently upgrade existing signed
+ cookies generated by Rails 3.x to avoid invalidating them when upgrading to Rails 4.x.
+
+ *Trevor Turk + Neeraj Singh*
+
+* Raise an `ArgumentError` when a clashing named route is defined.
+
+ *Trevor Turk*
+
+* Allow default url options to accept host with protocol such as `http://`
+
+ config.action_mailer.default_url_options = { host: "http://mydomain.com" }
+
+ *Richard Schneeman*
+
+* Ensure that digest authentication responds with a 401 status when a basic
+ header is received.
+
+ *Brad Dunbar*
+
+* Include I18n locale fallbacks in view lookup.
+ Fixes GH#3512.
+
+ *Juan Barreneche*
+
+* Integration and functional tests allow headers and rack env
+ variables to be passed when performing requests.
+ Fixes #6513.
+
+ Example:
+
+ # integration test
+ get "/success", {}, "HTTP_REFERER" => "http://test.com/",
+ "Accepts" => "text/plain, text/html"
+
+ # functional test
+ @request.headers["Accepts"] = "text/plain, text/html"
+
+ *Yves Senn*
+
+* Http::Headers respects headers that are not prefixed with HTTP_
+
+ *Yves Senn*
+
* Fix incorrectly appended square brackets to a multiple select box
if an explicit name has been given and it already ends with "[]"
@@ -60,6 +124,7 @@
*Thierry Zires*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser*
@@ -252,12 +317,12 @@
Client-IP and Remote-Addr headers, in that order. Document the rationale
for that decision, and describe the options that can be passed to the
RemoteIp middleware to change it.
- Fix #7979
+ Fixes #7979.
*André Arko*, *Steve Klabnik*, *Alexey Gaziev*
* Do not append second slash to `root_url` when using `trailing_slash: true`
- Fix #8700
+ Fixes #8700.
Before:
@@ -285,7 +350,7 @@
* Do not append `charset=` parameter when `head` is called with a
`:content_type` option.
- Fix #8661.
+ Fixes #8661.
*Yves Senn*
@@ -443,7 +508,7 @@
* Render every partial with a new `ActionView::PartialRenderer`. This resolves
issues when rendering nested partials.
- Fix #8197.
+ Fixes #8197.
*Yves Senn*
@@ -451,7 +516,7 @@
of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
contains `text/plain`.
- Fix #7976.
+ Fixes #7976.
*Joost Baaij*
@@ -467,7 +532,7 @@
check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
# => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
- Fix #8108.
+ Fixes #8108.
*Daniel Fox, Grant Hutchins & Trace Wax*
@@ -490,7 +555,7 @@
*Josh Peek*
* `assert_template` can be used to assert on the same template with different locals
- Fix #3675.
+ Fixes #3675.
*Yves Senn*
@@ -501,7 +566,7 @@
* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch*
* Warn when the `:locals` option is passed to `assert_template` outside of a view test case
- Fix #3415.
+ Fixes #3415.
*Yves Senn*
@@ -525,12 +590,12 @@
* Rename internal variables on `ActionController::TemplateAssertions` to prevent
naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore.
- Fix #7459.
+ Fixes #7459.
*Yves Senn*
* `resource` and `resources` don't modify the passed options hash.
- Fix #7777.
+ Fixes #7777.
*Yves Senn*
@@ -594,7 +659,7 @@
*Guillermo Iguaran*
* Log now displays the correct status code when an exception is raised.
- Fix #7646.
+ Fixes #7646.
*Yves Senn*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 03eeb841ee..cc8351a489 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency 'erubis', '~> 2.7.0'
s.add_development_dependency 'activemodel', version
- s.add_development_dependency 'tzinfo', '~> 0.3.33'
+ s.add_development_dependency 'tzinfo', '~> 0.3.37'
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 91864f2a35..bac994496e 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -285,10 +285,9 @@ module AbstractController
remove_possible_method(:_layout)
prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
name_clause = if name
- <<-RUBY
- lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
- RUBY
+ default_behavior
else
<<-RUBY
super
@@ -301,6 +300,7 @@ module AbstractController
when Symbol
<<-RUBY
#{_layout}.tap do |layout|
+ return #{default_behavior} if layout.nil?
unless layout.is_a?(String) || !layout
raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
"should have returned a String, false, or nil"
@@ -308,8 +308,12 @@ module AbstractController
end
RUBY
when Proc
- define_method :_layout_from_proc, &_layout
- _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
+ define_method :_layout_from_proc, &_layout
+ <<-RUBY
+ result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
+ return #{default_behavior} if result.nil?
+ result
+ RUBY
when false
nil
when true
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index f8e4cb4384..07f5c2586c 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -105,7 +105,7 @@ module AbstractController
#
# If a component extends the semantics of response_body
# (as Action Controller extends it to be anything that
- # responds to the method each), this method needs to
+ # responds to the method each), this method needs to be
# overriden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3d274e7dd7..7318c8b7ec 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -48,6 +48,11 @@ module ActionController
info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
end
+ def unpermitted_parameters(event)
+ unpermitted_keys = event.payload[:keys]
+ debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
+ end
+
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 2aa6b7adaf..af36ffa240 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -27,7 +27,7 @@ module ActionController
end
def visible_action?(action_name)
- action_methods.include?(action_name)
+ not hidden_actions.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index e295002b16..c7bb2dd147 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -299,6 +299,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 9d628c916f..fb664a69dd 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -98,6 +98,10 @@ module ActionController
def merge_default_headers(original, default)
Header.new self, super
end
+
+ def handle_conditional_get!
+ super unless committed?
+ end
end
def process(name)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index e4dcd3213f..23d70c9ea2 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
+require 'stringio'
module ActionController
# Raised when a required parameter is missing.
@@ -68,6 +69,8 @@ module ActionController
# ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
# in test and development environments, +false+ otherwise.
#
+ # Examples:
+ #
# params = ActionController::Parameters.new
# params.permitted? # => false
#
@@ -339,7 +342,8 @@ module ActionController
if unpermitted_keys.any?
case self.class.action_on_unpermitted_parameters
when :log
- ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}"
+ name = "unpermitted_parameters.action_controller"
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
when :raise
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
end
@@ -417,7 +421,7 @@ module ActionController
# Declaration { comment_ids: [] }.
array_of_permitted_scalars_filter(params, key)
else
- # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }.
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
if element.is_a?(Hash)
element = self.class.new(element) unless element.respond_to?(:permit)
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index e12bf0a1c6..41b5228872 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -42,13 +42,13 @@ module ActionController
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
- path = payload[:identifier]
next if payload[:virtual_path] # files don't have virtual path
+
+ path = payload[:identifier]
if path
@_files[path] += 1
@_files[path.split("/").last] += 1
end
-
end
end
@@ -451,37 +451,55 @@ module ActionController
end
- # Executes a request simulating GET HTTP method and set/volley the response
+ # Simulate a GET request with the given parameters.
+ #
+ # - +action+: The controller action to call.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+, a Hash, or a String that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # - +session+: A Hash of parameters to store in the session. This may be +nil+.
+ # - +flash+: A Hash of parameters to store in the flash. This may be +nil+.
+ #
+ # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
+ # Note that the request method is not verified. The different methods are
+ # available to make the tests more expressive.
def get(action, *args)
process(action, "GET", *args)
end
- # Executes a request simulating POST HTTP method and set/volley the response
+ # Simulate a POST request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def post(action, *args)
process(action, "POST", *args)
end
- # Executes a request simulating PATCH HTTP method and set/volley the response
+ # Simulate a PATCH request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def patch(action, *args)
process(action, "PATCH", *args)
end
- # Executes a request simulating PUT HTTP method and set/volley the response
+ # Simulate a PUT request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def put(action, *args)
process(action, "PUT", *args)
end
- # Executes a request simulating DELETE HTTP method and set/volley the response
+ # Simulate a DELETE request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def delete(action, *args)
process(action, "DELETE", *args)
end
- # Executes a request simulating HEAD HTTP method and set/volley the response
+ # Simulate a HEAD request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def head(action, *args)
process(action, "HEAD", *args)
end
- # Executes a request simulating OPTIONS HTTP method and set/volley the response
+ # Simulate a OPTIONS request with the given parameters and set/volley the response.
+ # See +#get+ for more details.
def options(action, *args)
process(action, "OPTIONS", *args)
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 0d6015d993..f9b278349e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -92,7 +92,7 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
- SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+ SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate]
def cache_control_segments
if cache_control = self[CACHE_CONTROL]
@@ -108,7 +108,7 @@ module ActionDispatch
cache_control_segments.each do |segment|
directive, argument = segment.split('=', 2)
- if SPESHUL_KEYS.include? directive
+ if SPECIAL_KEYS.include? directive
key = directive.tr('-', '_')
cache_control[key.to_sym] = argument || true
else
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index dc04d4577b..2666cd4b0a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,38 +1,62 @@
module ActionDispatch
module Http
class Headers
+ CGI_VARIABLES = %w(
+ CONTENT_TYPE CONTENT_LENGTH
+ HTTPS AUTH_TYPE GATEWAY_INTERFACE
+ PATH_INFO PATH_TRANSLATED QUERY_STRING
+ REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER
+ REQUEST_METHOD SCRIPT_NAME
+ SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
+ )
+ HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
+
include Enumerable
+ attr_reader :env
def initialize(env = {})
- @headers = env
+ @env = env
+ end
+
+ def [](key)
+ @env[env_name(key)]
end
- def [](header_name)
- @headers[env_name(header_name)]
+ def []=(key, value)
+ @env[env_name(key)] = value
end
- def []=(k,v); @headers[k] = v; end
- def key?(k); @headers.key? k; end
+ def key?(key); @env.key? key; end
alias :include? :key?
- def fetch(header_name, *args, &block)
- @headers.fetch env_name(header_name), *args, &block
+ def fetch(key, *args, &block)
+ @env.fetch env_name(key), *args, &block
end
def each(&block)
- @headers.each(&block)
+ @env.each(&block)
end
- private
+ def merge(headers_or_env)
+ headers = Http::Headers.new(env.dup)
+ headers.merge!(headers_or_env)
+ headers
+ end
- # Converts a HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
- def env_name(header_name)
- @headers.include?(header_name) ? header_name : cgi_name(header_name)
+ def merge!(headers_or_env)
+ headers_or_env.each do |key, value|
+ self[env_name(key)] = value
+ end
end
- def cgi_name(k)
- "HTTP_#{k.upcase.gsub(/-/, '_')}"
+ private
+ def env_name(key)
+ key = key.to_s
+ if key =~ HTTP_HEADER
+ key = key.upcase.tr('-', '_')
+ key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
+ end
+ key
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 446862aad0..246d9c121a 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -18,7 +18,7 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- encode_params(params).with_indifferent_access
+ params.with_indifferent_access
end
end
alias :params :parameters
@@ -50,40 +50,33 @@ module ActionDispatch
private
+ # Convert nested Hash to HashWithIndifferentAccess
+ # and UTF-8 encode both keys and values in nested Hash.
+ #
# TODO: Validate that the characters are UTF-8. If they aren't,
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
- def encode_params(params)
+ def normalize_encode_params(params)
if params.is_a?(String)
return params.force_encoding(Encoding::UTF_8).encode!
elsif !params.is_a?(Hash)
return params
end
+ new_hash = {}
params.each do |k, v|
- case v
- when Hash
- encode_params(v)
- when Array
- v.map! {|el| encode_params(el) }
- else
- encode_params(v)
- end
- end
- end
-
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess
- def normalize_parameters(value)
- case value
- when Hash
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- when Array
- value.map { |e| normalize_parameters(e) }
- else
- value
+ new_key = k.is_a?(String) ? k.dup.force_encoding("UTF-8").encode! : k
+ new_hash[new_key] =
+ case v
+ when Hash
+ normalize_encode_params(v)
+ when Array
+ v.map! {|el| normalize_encode_params(el) }
+ else
+ normalize_encode_params(v)
+ end
end
+ new_hash.with_indifferent_access
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 7b04d6e851..ebd87c40b5 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -156,14 +156,29 @@ module ActionDispatch
@original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
end
+ # Returns the +String+ full path including params of the last URL requested.
+ #
+ # # get "/articles"
+ # request.fullpath # => "/articles"
+ #
+ # # get "/articles?page=2"
+ # request.fullpath # => "/articles?page=2"
def fullpath
@fullpath ||= super
end
+ # Returns the original request URL as a +String+.
+ #
+ # # get "/articles?page=2"
+ # request.original_url # => "http://www.example.com/articles?page=2"
def original_url
base_url + original_fullpath
end
+ # The +String+ MIME type of the request.
+ #
+ # # get "/articles"
+ # request.media_type # => "application/x-www-form-urlencoded"
def media_type
content_mime_type.to_s
end
@@ -256,7 +271,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
+ @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {})
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -264,7 +279,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
+ @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {})
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index b4df2b691f..b57c84dec8 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -75,16 +75,16 @@ module ActionDispatch
end
module Upload # :nodoc:
- # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace
- # file upload hash with UploadedFile objects
- def normalize_parameters(value)
+ # Replace file upload hash with UploadedFile objects
+ # when normalize and encode parameters.
+ def normalize_encode_params(value)
if Hash === value && value.has_key?(:tempfile)
UploadedFile.new(value)
else
super
end
end
- private :normalize_parameters
+ private :normalize_encode_params
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 97ac462411..ab5399c8ea 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -59,8 +59,9 @@ module ActionDispatch
result = ""
unless options[:only_path]
+ protocol = extract_protocol(options)
unless options[:protocol] == false
- result << (options[:protocol] || "http")
+ result << protocol
result << ":" unless result.match(%r{:|//})
end
result << "//" unless result.match("//")
@@ -83,6 +84,16 @@ module ActionDispatch
end
end
+ # Extracts protocol http:// or https:// from options[:host]
+ # needs to be called whether the :protocol is being used or not
+ def extract_protocol(options)
+ if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/)
+ options[:protocol] ||= match[1]
+ options[:host] = match[2]
+ end
+ options[:protocol] || "http"
+ end
+
def host_or_subdomain_and_domain(options)
return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 36a0db6e61..f21d1d4ee5 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -86,7 +87,8 @@ module ActionDispatch
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
- TOKEN_KEY = "action_dispatch.secret_token".freeze
+ SECRET_TOKEN = "action_dispatch.secret_token".freeze
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -94,8 +96,68 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ module ChainedCookieJars
+ # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
+ #
+ # cookies.permanent[:prefers_open_id] = true
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ #
+ # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
+ #
+ # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
+ #
+ # cookies.permanent.signed[:remember_me] = current_user.id
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
+ # 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.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||= begin
+ if @options[:upgrade_legacy_signed_cookie_jar]
+ UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ else
+ SignedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+ end
+
+ # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
+ def signed_using_old_secret #:nodoc:
+ @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:secret_token]), @options)
+ end
+
+ # 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.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
class CookieJar #:nodoc:
- include Enumerable
+ include Enumerable, ChainedCookieJars
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,7 +177,10 @@ module ActionDispatch
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
- token_key: env[TOKEN_KEY] }
+ secret_token: env[SECRET_TOKEN],
+ secret_key_base: env[SECRET_KEY_BASE],
+ upgrade_legacy_signed_cookie_jar: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ }
end
def self.build(request)
@@ -232,59 +297,6 @@ module ActionDispatch
@cookies.each_key{ |k| delete(k, options) }
end
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
- #
- # cookies.permanent[:prefers_open_id] = true
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- #
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
- #
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
- #
- # cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
- # 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), an ActiveSupport::MessageVerifier::InvalidSignature exception will
- # be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.signed[:discount] = 45
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
- #
- # cookies.signed[:discount] # => 45
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this
- def signed_using_old_secret #:nodoc:
- @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options)
- end
-
- # 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), an ActiveSupport::MessageVerifier::InvalidSignature exception
- # will be raised.
- #
- # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
- #
- # Example:
- #
- # cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
- #
- # cookies.encrypted[:discount] # => 45
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
def write(headers)
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
@@ -306,6 +318,8 @@ module ActionDispatch
end
class PermanentCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@@ -326,26 +340,11 @@ module ActionDispatch
options[:expires] = 20.years.from_now
@parent_jar[key] = options
end
-
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
end
class SignedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
@@ -372,26 +371,42 @@ module ActionDispatch
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
+ end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
+ # config.secret_token and config.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:
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
end
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ def [](name)
+ if signed_message = @parent_jar[name]
+ verify_signed_message(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ end
end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ def verify_signed_message(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
+ def verify_and_upgrade_legacy_signed_message(name, signed_message)
+ @legacy_verifier.verify(signed_message).tap do |value|
+ self[name] = value
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
end
end
class EncryptedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::DummyKeyGenerator === key_generator
raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
@@ -425,23 +440,6 @@ module ActionDispatch
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
-
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
-
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
-
- def method_missing(method, *arguments, &block)
- ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
- "You probably want to try this method over the parent CookieJar."
- end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 93a2b52996..8879291dbd 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -101,7 +101,7 @@ module ActionDispatch
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
- (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
)$)
}x
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 619dd22ec1..7fb4719fa0 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -403,11 +403,19 @@ module ActionDispatch
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
+ if name && named_routes[name]
+ raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \
+ "You may have defined two routes with the same name using the `:as` option, or "
+ "you may be overriding a route already defined by a resource with the same naming. " \
+ "For the latter, you can restrict the routes created with `resources` as explained here: \n" \
+ "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ end
+
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name && !named_routes[name]
+ named_routes[name] = route if name
route
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index ed4e88aab6..56c31255f3 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -17,7 +17,7 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
# merged into the Rack env hash.
#
# This method returns a Response object, which one can use to
@@ -28,44 +28,44 @@ module ActionDispatch
#
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
- def get(path, parameters = nil, headers = nil)
- process :get, path, parameters, headers
+ def get(path, parameters = nil, headers_or_env = nil)
+ process :get, path, parameters, headers_or_env
end
# Performs a POST request with the given parameters. See +#get+ for more
# details.
- def post(path, parameters = nil, headers = nil)
- process :post, path, parameters, headers
+ def post(path, parameters = nil, headers_or_env = nil)
+ process :post, path, parameters, headers_or_env
end
# Performs a PATCH request with the given parameters. See +#get+ for more
# details.
- def patch(path, parameters = nil, headers = nil)
- process :patch, path, parameters, headers
+ def patch(path, parameters = nil, headers_or_env = nil)
+ process :patch, path, parameters, headers_or_env
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
- def put(path, parameters = nil, headers = nil)
- process :put, path, parameters, headers
+ def put(path, parameters = nil, headers_or_env = nil)
+ process :put, path, parameters, headers_or_env
end
# Performs a DELETE request with the given parameters. See +#get+ for
# more details.
- def delete(path, parameters = nil, headers = nil)
- process :delete, path, parameters, headers
+ def delete(path, parameters = nil, headers_or_env = nil)
+ process :delete, path, parameters, headers_or_env
end
# Performs a HEAD request with the given parameters. See +#get+ for more
# details.
- def head(path, parameters = nil, headers = nil)
- process :head, path, parameters, headers
+ def head(path, parameters = nil, headers_or_env = nil)
+ process :head, path, parameters, headers_or_env
end
# Performs a OPTIONS request with the given parameters. See +#get+ for
# more details.
- def options(path, parameters = nil, headers = nil)
- process :options, path, parameters, headers
+ def options(path, parameters = nil, headers_or_env = nil)
+ process :options, path, parameters, headers_or_env
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
@@ -74,11 +74,11 @@ module ActionDispatch
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash.
- def xml_http_request(request_method, path, parameters = nil, headers = nil)
- headers ||= {}
- headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers)
+ def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
+ headers_or_env ||= {}
+ headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ process(request_method, path, parameters, headers_or_env)
end
alias xhr :xml_http_request
@@ -95,40 +95,40 @@ module ActionDispatch
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers = nil)
- process(http_method, path, parameters, headers)
+ def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
+ process(http_method, path, parameters, headers_or_env)
follow_redirect! while redirect?
status
end
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def get_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:get, path, parameters, headers)
+ def get_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:get, path, parameters, headers_or_env)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def post_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:post, path, parameters, headers)
+ def post_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:post, path, parameters, headers_or_env)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def patch_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:patch, path, parameters, headers)
+ def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:patch, path, parameters, headers_or_env)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def put_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:put, path, parameters, headers)
+ def put_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:put, path, parameters, headers_or_env)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def delete_via_redirect(path, parameters = nil, headers = nil)
- request_via_redirect(:delete, path, parameters, headers)
+ def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
+ request_via_redirect(:delete, path, parameters, headers_or_env)
end
end
@@ -268,8 +268,7 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_env = nil)
- rack_env ||= {}
+ def process(method, path, parameters = nil, headers_or_env = nil)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -300,10 +299,12 @@ module ActionDispatch
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
+ # this modifies the passed env directly
+ Http::Headers.new(env).merge!(headers_or_env || {})
session = Rack::Test::Session.new(_mock_session)
- env.merge!(rack_env)
+ env.merge!(env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 5c87a9cd7c..b5e47d78d1 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,10 +1,11 @@
module ActionPack
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActionPack as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActionPack.version.segments
+ STRING = ActionPack.version.to_s
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ec89da71a1..36cfb7fca7 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -433,7 +433,7 @@ module ActionView
builder = instantiate_builder(object_name, object, options)
output = capture(builder, &block)
- html_options[:multipart] = builder.multipart?
+ html_options[:multipart] ||= builder.multipart?
form_tag(options[:url] || {}, html_options) { output }
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 4e4816d983..d61cc0f304 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -43,7 +43,13 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
+ register_detail(:locale) do
+ locales = [I18n.locale]
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
+ locales << I18n.default_locale
+ locales.uniq!
+ locales
+ end
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
register_detail(:handlers){ Template::Handlers.extensions }
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 720890eeb9..946db1df79 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -324,7 +324,8 @@ module ActionView
end
def locals_code #:nodoc:
- @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
+ # Double assign to suppress the dreaded 'assigned but unused variable' warning
+ @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join
end
def method_name #:nodoc:
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
index 6b4ececda2..30b6b8b141 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb
@@ -77,7 +77,7 @@ module HTML
# A regular expression of the valid characters used to separate protocols like
# the ':' in 'http://foo.com'
- self.protocol_separator = /:|(&#0*58)|(&#x70)|(%|&#37;)3A/
+ self.protocol_separator = /:|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i
# Specifies a Set of HTML attributes that can have URIs.
self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc))
@@ -121,8 +121,8 @@ module HTML
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
# gauntlet
- if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ ||
- style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/
+ if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
+ style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
return ''
end
@@ -133,7 +133,7 @@ module HTML
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
unless val.split().any? do |keyword|
!allowed_css_keywords.include?(keyword) &&
- keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/
+ keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
end
clean << prop + ': ' + val + ';'
end
@@ -182,7 +182,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 1090af3060..8cba049485 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -259,7 +259,7 @@ module AbstractController
end
class TestCallbacksWithArgs < ActiveSupport::TestCase
- test "callbacks still work when invoking process with multiple args" do
+ test "callbacks still work when invoking process with multiple arguments" do
controller = CallbacksWithArgs.new
controller.process(:index, " Howdy!")
assert_equal "Hello world Howdy!", controller.response_body
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index c14d24905b..5709ad0378 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -42,7 +42,7 @@ module AbstractController
end
end
- test "generated methods call custom with args received" do
+ test "generated methods call custom with arguments received" do
collector = MyCollector.new
collector.html
collector.text(:foo)
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 558a45b87f..92baad4523 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -72,13 +72,21 @@ module AbstractControllerTests
end
class WithProc < Base
- layout proc { |c| "overwrite" }
+ layout proc { "overwrite" }
def index
render :template => ActionView::Template::Text.new("Hello proc!")
end
end
+ class WithProcReturningNil < Base
+ layout proc { nil }
+
+ def index
+ render template: ActionView::Template::Text.new("Hello nil!")
+ end
+ end
+
class WithZeroArityProc < Base
layout proc { "overwrite" }
@@ -249,6 +257,12 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
+ test "when layout is specified as a proc and the proc retuns nil, don't use a layout" do
+ controller = WithProcReturningNil.new
+ controller.process(:index)
+ assert_equal "Hello nil!", controller.response_body
+ end
+
test "when layout is specified as a proc without parameters it works just the same" do
controller = WithZeroArityProc.new
controller.process(:index)
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 9b42e7631f..d4f18d55a6 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -68,6 +68,12 @@ class RecordIdentifierWithoutDeprecationController < ActionController::Base
include ActionView::RecordIdentifier
end
+class ActionMissingController < ActionController::Base
+ def action_missing(action)
+ render :text => "Response for #{action}"
+ end
+end
+
class ControllerClassTests < ActiveSupport::TestCase
def test_controller_path
@@ -186,6 +192,12 @@ class PerformActionTest < ActionController::TestCase
assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
end
+
+ def test_action_missing_should_work
+ use_controller ActionMissingController
+ get :arbitrary_action
+ assert_equal "Response for arbitrary_action", @response.body
+ end
end
class UrlOptionsTest < ActionController::TestCase
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 537de7a2dd..4287856550 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -249,6 +249,14 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
+ test "when sent a basic auth header, returns Unauthorized" do
+ @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq='
+
+ get :display
+
+ assert_response :unauthorized
+ end
+
private
def encode_credentials(options)
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 72b882539c..c3bdf74d93 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -573,6 +573,21 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
def test_generate_url_without_controller
assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
end
+
+ def test_pass_headers
+ get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"
+
+ assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
+ assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
+ end
+
+ def test_pass_env
+ get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com"
+
+ assert_equal "http://test.com", @request.env["HTTP_HOST"]
+ assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
+ end
+
end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 71bcfd664e..34304cf640 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -94,6 +94,18 @@ class HasOwnLayoutController < LayoutTest
layout 'item'
end
+class HasNilLayoutSymbol < LayoutTest
+ layout :nilz
+
+ def nilz
+ nil
+ end
+end
+
+class HasNilLayoutProc < LayoutTest
+ layout proc { nil }
+end
+
class PrependsViewPathController < LayoutTest
def hello
prepend_view_path File.dirname(__FILE__) + '/../fixtures/layout_tests/alt/'
@@ -142,6 +154,18 @@ class LayoutSetInResponseTest < ActionController::TestCase
assert_template :layout => "layouts/item"
end
+ def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutSymbol.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
+ def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default
+ @controller = HasNilLayoutProc.new
+ get :hello
+ assert_template layout: "layouts/layout_test"
+ end
+
def test_layout_only_exception_when_included
@controller = OnlyLayoutController.new
get :hello
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 3b1a07d7af..5755444a65 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -48,6 +48,10 @@ module ActionController
end
response.stream.close
end
+
+ def with_stale
+ render :text => 'stale' if stale?(:etag => "123")
+ end
end
tests TestController
@@ -117,5 +121,16 @@ module ActionController
assert_equal 'zomg', response.body
assert response.stream.closed?, 'stream should be closed'
end
+
+ def test_stale_without_etag
+ get :with_stale
+ assert_equal 200, @response.status.to_i
+ end
+
+ def test_stale_with_etag
+ @request.if_none_match = Digest::MD5.hexdigest("123")
+ get :with_stale
+ assert_equal 304, @response.status.to_i
+ end
end
end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index bac1d02977..6b02eedaed 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -25,4 +25,13 @@ class LocalizedTemplatesTest < ActionController::TestCase
ensure
I18n.locale = old_locale
end
+
+ def test_use_fallback_locales
+ I18n.locale = :"de-AT"
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
index 43a8c05cda..c3c549fbfc 100644
--- a/actionpack/test/controller/output_escaping_test.rb
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -11,8 +11,6 @@ class OutputEscapingTest < ActiveSupport::TestCase
end
test "escapeHTML shouldn't touch explicitly safe strings" do
- # TODO this seems easier to compose and reason about, but
- # this should be verified
assert_equal "<", ERB::Util.h("<".html_safe)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 93e94f0f48..978c5aa7ac 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -908,12 +908,13 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal set.routes.first, set.named_routes[:hello]
end
- def test_earlier_named_routes_take_precedence
- set.draw do
- get '/hello/world' => 'a#b', :as => 'hello'
- get '/hello' => 'a#b', :as => 'hello'
+ def test_duplicate_named_route_raises_rather_than_pick_precedence
+ assert_raise ArgumentError do
+ set.draw do
+ get '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello' => 'a#b', :as => 'hello'
+ end
end
- assert_equal set.routes.first, set.named_routes[:hello]
end
def setup_named_route_test
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index df31338f09..38b9794b4d 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -57,6 +57,10 @@ class TestCaseTest < ActionController::TestCase
render :text => request.protocol
end
+ def test_headers
+ render text: request.headers.env.to_json
+ end
+
def test_html_output
render :text => <<HTML
<html>
@@ -626,6 +630,24 @@ XML
assert_equal 2004, page[:year]
end
+ test "set additional HTTP headers" do
+ @request.headers['Referer'] = "http://nohost.com/home"
+ @request.headers['Content-Type'] = "application/rss+xml"
+ get :test_headers
+ parsed_env = JSON.parse(@response.body)
+ assert_equal "http://nohost.com/home", parsed_env["HTTP_REFERER"]
+ assert_equal "application/rss+xml", parsed_env["CONTENT_TYPE"]
+ end
+
+ test "set additional env variables" do
+ @request.headers['HTTP_REFERER'] = "http://example.com/about"
+ @request.headers['CONTENT_TYPE'] = "application/json"
+ get :test_headers
+ parsed_env = JSON.parse(@response.body)
+ assert_equal "http://example.com/about", parsed_env["HTTP_REFERER"]
+ assert_equal "application/json", parsed_env["CONTENT_TYPE"]
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 5ada5a7603..c532e0b8cc 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,6 +1,15 @@
require 'abstract_unit'
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
+require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -67,6 +76,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def get_signed_cookie
+ cookies.signed[:user_id]
+ head :ok
+ end
+
def set_encrypted_cookie
cookies.encrypted[:foo] = 'bar'
head :ok
@@ -421,6 +435,55 @@ class CookiesTest < ActionController::TestCase
}
end
+ def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_signed_cookie_is_nil_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "user_id=45"
+ get :get_signed_cookie
+
+ assert_equal nil, @controller.send(:cookies).signed[:user_id]
+ assert_equal nil, @response.cookies["user_id"]
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
@@ -669,3 +732,5 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+end
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index 42432510c3..9e37b96951 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -1,41 +1,137 @@
-require 'abstract_unit'
+require "abstract_unit"
class HeaderTest < ActiveSupport::TestCase
- def setup
+ setup do
@headers = ActionDispatch::Http::Headers.new(
- "HTTP_CONTENT_TYPE" => "text/plain"
+ "CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"
)
end
- def test_each
+ test "#new does not normalize the data" do
+ headers = ActionDispatch::Http::Headers.new(
+ "Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com")
+
+ assert_equal({"Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com"}, headers.env)
+ end
+
+ test "#env returns the headers as env variables" do
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#each iterates through the env variables" do
headers = []
@headers.each { |pair| headers << pair }
- assert_equal [["HTTP_CONTENT_TYPE", "text/plain"]], headers
+ assert_equal [["CONTENT_TYPE", "text/plain"],
+ ["HTTP_REFERER", "/some/page"]], headers
+ end
+
+ test "set new headers" do
+ @headers["Host"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
+ end
+
+ test "headers can contain numbers" do
+ @headers["Content-MD5"] = "Q2hlY2sgSW50ZWdyaXR5IQ=="
+
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["Content-MD5"]
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["HTTP_CONTENT_MD5"]
+ end
+
+ test "set new env variables" do
+ @headers["HTTP_HOST"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
end
- def test_setter
- @headers['foo'] = "bar"
- assert_equal "bar", @headers['foo']
+ test "key?" do
+ assert @headers.key?("CONTENT_TYPE")
+ assert @headers.include?("CONTENT_TYPE")
end
- def test_key?
- assert @headers.key?('HTTP_CONTENT_TYPE')
- assert @headers.include?('HTTP_CONTENT_TYPE')
+ test "fetch with block" do
+ assert_equal "omg", @headers.fetch("notthere") { "omg" }
end
- def test_fetch_with_block
- assert_equal 'omg', @headers.fetch('notthere') { 'omg' }
+ test "accessing http header" do
+ assert_equal "/some/page", @headers["Referer"]
+ assert_equal "/some/page", @headers["referer"]
+ assert_equal "/some/page", @headers["HTTP_REFERER"]
end
- test "content type" do
+ test "accessing special header" do
assert_equal "text/plain", @headers["Content-Type"]
assert_equal "text/plain", @headers["content-type"]
assert_equal "text/plain", @headers["CONTENT_TYPE"]
- assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
end
test "fetch" do
assert_equal "text/plain", @headers.fetch("content-type", nil)
- assert_equal "not found", @headers.fetch('not-found', 'not found')
+ assert_equal "not found", @headers.fetch("not-found", "not found")
+ end
+
+ test "#merge! headers with mutation" do
+ @headers.merge!("Host" => "http://example.test",
+ "Content-Type" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.test",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#merge! env with mutation" do
+ @headers.merge!("HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "merge without mutation" do
+ combined = @headers.merge("HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, combined.env)
+
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "env variables with . are not modified" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge! "rack.input" => "",
+ "rack.request.cookie_hash" => "",
+ "action_dispatch.logger" => ""
+
+ assert_equal(["action_dispatch.logger",
+ "rack.input",
+ "rack.request.cookie_hash"], headers.env.keys.sort)
+ end
+
+ test "symbols are treated as strings" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge!(:SERVER_NAME => "example.com",
+ "HTTP_REFERER" => "/",
+ :Host => "test.com")
+ assert_equal "example.com", headers["SERVER_NAME"]
+ assert_equal "/", headers[:HTTP_REFERER]
+ assert_equal "test.com", headers["HTTP_HOST"]
+ end
+
+ test "headers directly modifies the passed environment" do
+ env = {"HTTP_REFERER" => "/"}
+ headers = ActionDispatch::Http::Headers.new(env)
+ headers['Referer'] = "http://example.com/"
+ headers.merge! "CONTENT_TYPE" => "text/plain"
+ assert_equal({"HTTP_REFERER"=>"http://example.com/",
+ "CONTENT_TYPE"=>"text/plain"}, env)
end
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 3b008fdff0..e5e28c28be 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -21,7 +21,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
mount SprocketsApp, :at => "/sprockets"
mount SprocketsApp => "/shorthand"
- mount FakeEngine, :at => "/fakeengine"
+ mount FakeEngine, :at => "/fakeengine", :as => :fake
mount FakeEngine, :at => "/getfake", :via => :get
scope "/its_a" do
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 399f15199c..3c30a705e9 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -1,13 +1,15 @@
+# encoding: utf-8
require 'abstract_unit'
class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
class << self
- attr_accessor :last_request_parameters
+ attr_accessor :last_request_parameters, :last_parameters
end
def parse
self.class.last_request_parameters = request.request_parameters
+ self.class.last_parameters = request.parameters
head :ok
end
@@ -30,6 +32,23 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param'))
end
+ test "parse single utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ parse_multipart('single_utf8_param'), "request.request_parameters")
+ assert_equal(
+ 'Iñtërnâtiônàlizætiøn_value',
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
+ test "parse bracketed utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
+ 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
+ parse_multipart('bracketed_utf8_param'), "request.request_parameters")
+ assert_equal(
+ {'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
test "parses text file" do
params = parse_multipart('text_file')
assert_equal %w(file foo), params.keys.sort
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index e9b59f55a7..9169658c22 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -164,7 +164,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
return
end
- object.each do |k,v|
+ object.each_value do |v|
case v
when Hash
assert_utf8(v)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2bf7056ff7..df359ba77d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2577,22 +2577,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raises(ActionController::UrlGenerationError){ list_todo_path(:list_id => '2', :id => '1') }
end
- def test_named_routes_collision_is_avoided_unless_explicitly_given_as
- draw do
- scope :as => "routes" do
- get "/c/:id", :as => :collision, :to => "collision#show"
- get "/collision", :to => "collision#show"
- get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
- end
- end
-
- assert_equal "/c/1", routes_collision_path(1)
- assert_equal "/fc/1", routes_forced_collision_path(1)
- end
-
def test_redirect_argument_error
routes = Class.new { include ActionDispatch::Routing::Redirection }.new
assert_raises(ArgumentError) { routes.redirect Object.new }
@@ -2604,9 +2588,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/c/:id", :as => :collision, :to => "collision#show"
get "/collision", :to => "collision#show"
get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
end
end
@@ -2657,6 +2638,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_duplicate_route_name_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ get '/duplicate', :to => 'duplicate#show', :as => 'collision'
+ end
+ end
+ end
+
+ def test_duplicate_route_name_via_resources_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ resources :collisions
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ end
+ end
+ end
+
def test_nested_route_in_nested_resource
draw do
resources :posts, :only => [:index, :show] do
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index e56e8ddc57..4123529092 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -48,6 +48,14 @@ module TestUrlGeneration
https!
assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
end
+
+ test "extracting protocol from host when protocol not present" do
+ assert_equal "httpz://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: nil)
+ end
+
+ test "formatting host when protocol is present" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://")
+ end
end
end
diff --git a/actionpack/test/fixtures/multipart/bracketed_utf8_param b/actionpack/test/fixtures/multipart/bracketed_utf8_param
new file mode 100644
index 0000000000..976ca44a45
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/bracketed_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/single_utf8_param b/actionpack/test/fixtures/multipart/single_utf8_param
new file mode 100644
index 0000000000..b86f62d1e1
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/single_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 268bab6ad2..dff0b8bdc2 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -2791,8 +2791,8 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_html_options_adds_options_to_form_tag
- form_for(@post, html: { id: 'some_form', class: 'some_class' }) do |f| end
- expected = whole_form("/posts/123", "some_form", "some_class", method: "patch")
+ form_for(@post, html: { id: 'some_form', class: 'some_class', multipart: true }) do |f| end
+ expected = whole_form("/posts/123", "some_form", "some_class", method: "patch", multipart: "multipart/form-data")
assert_dom_equal expected, output_buffer
end
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index d9b57776c9..b1c1b83807 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -200,6 +200,7 @@ class SanitizerTest < ActionController::TestCase
%(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
%(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
%(<IMG SRC=" &#14; javascript:alert('XSS');">),
+ %(<IMG SRC="javascript&#x3a;alert('XSS');">),
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
assert_sanitized img_hack, "<img>"
@@ -279,6 +280,11 @@ class SanitizerTest < ActionController::TestCase
assert_equal '', sanitize_css(raw)
end
+ def test_should_sanitize_across_newlines
+ raw = %(\nwidth:\nexpression(alert('XSS'));\n)
+ assert_equal '', sanitize_css(raw)
+ end
+
def test_should_sanitize_img_vbscript
assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
end
@@ -299,6 +305,15 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
end
+ def test_x03a
+ assert_sanitized %(<a href="javascript&#x3a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3a;//legit">), %(<a href="http://legit">)
+ assert_sanitized %(<a href="javascript&#x3A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3A;//legit">), %(<a href="http://legit">)
+ end
+
protected
def assert_sanitized(input, expected = nil)
@sanitizer ||= HTML::WhiteListSanitizer.new
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 5d87c96605..e359f47975 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -51,7 +51,6 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- # TODO: missing test cases
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com")
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index c6d7b0b5d3..e4c80b1bf8 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,31 @@
## Rails 4.0.0 (unreleased) ##
+* Add ActiveModel::Errors#full_messages_for, a method that returns all the error
+ messages for a given attribute.
+
+ *Volodymyr Shatsky*
+
+* Added a method so that validations can be easily cleared on a model.
+ For example:
+
+ class Person
+ include ActiveModel::Validations
+
+ validates_uniqueness_of :first_name
+ validate :cannot_be_robot
+
+ def cannot_be_robot
+ errors.add(:base, 'A person cannot be a robot') if person_is_robot
+ end
+ end
+
+ Now, if someone runs `Person.clear_validators!`, then the following occurs:
+
+ Person.validators # => []
+ Person._validate_callbacks.empty? # => true
+
+ *John Wang*
+
* `has_secure_password` does not fail the confirmation validation
when assigning empty String to `password` and `password_confirmation`.
@@ -70,7 +96,7 @@
*Yves Senn*
-* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`.
+* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_password`.
*Brian Cardarella + Jeremy Kemper + Trevor Turk*
@@ -111,7 +137,7 @@
* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default
propagate `:skip_types, :dasherize, :camelize` keys to included associations.
- It can be overriden on each association by explicitly specifying the option on one
+ It can be overridden on each association by explicitly specifying the option on one
or more associations
*Anthony Alberto*
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 963e52bff3..485eb1a40a 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -352,6 +352,20 @@ module ActiveModel
map { |attribute, message| full_message(attribute, message) }
end
+ # Returns all the full error messages for a given attribute in an array.
+ #
+ # class Person
+ # validates_presence_of :name, :email
+ # validates_length_of :name, in: 5..30
+ # end
+ #
+ # person = Person.create()
+ # person.errors.full_messages_for(:name)
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
+ def full_messages_for(attribute)
+ (get(attribute) || []).map { |message| full_message(attribute, message) }
+ end
+
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 9324a1ad0a..474cb0aea0 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -43,8 +43,13 @@ module ActiveModel
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
- gem 'bcrypt-ruby', '~> 3.0.0'
- require 'bcrypt'
+ begin
+ gem 'bcrypt-ruby', '~> 3.0.0'
+ require 'bcrypt'
+ rescue LoadError
+ $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
+ raise
+ end
attr_reader :password
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 2db4a25f61..714cc95b9a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -169,6 +169,49 @@ module ActiveModel
_validators.values.flatten.uniq
end
+ # Clears all of the validators and validations.
+ #
+ # Note that this will clear anything that is being used to validate
+ # the model for both the +validates_with+ and +validate+ methods.
+ # It clears the validators that are created with an invocation of
+ # +validates_with+ and the callbacks that are set by an invocation
+ # of +validate+.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # validates_with MyValidator
+ # validates_with OtherValidator, on: :create
+ # validates_with StrictValidator, strict: true
+ # validate :cannot_be_robot
+ #
+ # def cannot_be_robot
+ # errors.add(:base, 'A person cannot be a robot') if person_is_robot
+ # end
+ # end
+ #
+ # Person.validators
+ # # => [
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
+ # # ]
+ #
+ # If one runs Person.clear_validators! and then checks to see what
+ # validators this class has, you would obtain:
+ #
+ # Person.validators # => []
+ #
+ # Also, the callback set by +validate :cannot_be_robot+ will be erased
+ # so that:
+ #
+ # Person._validate_callbacks.empty? # => true
+ #
+ def clear_validators!
+ reset_callbacks(:validate)
+ _validators.clear
+ end
+
# List all validators that are being used to validate a specific attribute.
#
# class Person
@@ -333,7 +376,4 @@ module ActiveModel
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
- filename = File.basename(path)
- require "active_model/validations/#{filename}"
-end
+Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 7586b02037..35c4b30999 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,10 +1,11 @@
module ActiveModel
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveModel as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments
+ STRING = ActiveModel.version.to_s
end
end
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index baaf842222..d3ec78157c 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -194,7 +194,7 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_raises(NoMethodError) { ModelWithAttributes.new.foo }
end
- test 'acessing a suffixed attribute' do
+ test 'accessing a suffixed attribute' do
m = ModelWithAttributes2.new
m.attributes = { 'foo' => 'bar' }
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 086e7266ff..c4c34b0be7 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -107,7 +107,7 @@ class CallbacksTest < ActiveModel::TestCase
test "after_create callbacks with both callbacks declared in one line" do
assert_equal ["callback1", "callback2"], Violin1.new.create.history
end
- test "after_create callbacks with both callbacks declared in differnt lines" do
+ test "after_create callbacks with both callbacks declared in different lines" do
assert_equal ["callback1", "callback2"], Violin2.new.create.history
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 51dcfc37d8..139fb6795a 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -216,6 +216,26 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
end
+
+ test 'full_messages_for should contain all the messages for a 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)
+ end
+
+ test 'full_messages_for should not contain messages for another 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)
+ end
+
+ test "full_messages_for should return an empty array in case if errors hash doesn't contain a given attribute" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal [], person.errors.full_messages_for(:email)
+ end
test 'full_message should return the given message if attribute equals :base' do
person = Person.new
@@ -225,6 +245,7 @@ class ErrorsTest < ActiveModel::TestCase
test 'full_message should return 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")
end
test 'should return a JSON hash representation of the errors' do
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 38ba3cc152..aa683f4152 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -245,7 +245,7 @@ class NamingHelpersTest < ActiveModel::TestCase
end
def test_uncountable
- assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable"
+ assert uncountable?(@uncountable), "Expected 'sheep' to be uncountable"
assert !uncountable?(@klass), "Expected 'contact' to be countable"
end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 457f553661..daa7a8d5c4 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -50,7 +50,7 @@ class ValidatesWithTest < ActiveModel::TestCase
end
end
- test "vaidation with class that adds errors" do
+ test "validation with class that adds errors" do
Topic.validates_with(ValidatorThatAddsErrors)
topic = Topic.new
assert topic.invalid?, "A class that adds errors causes the record to be invalid"
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index a9d32808da..2934e70c2f 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -26,11 +26,11 @@ class ValidationsTest < ActiveModel::TestCase
def test_single_field_validation
r = Reply.new
r.title = "There's no content!"
- assert r.invalid?, "A reply without content shouldn't be saveable"
+ assert r.invalid?, "A reply without content shouldn't be savable"
assert r.after_validation_performed, "after_validation callback should be called"
r.content = "Messa content!"
- assert r.valid?, "A reply with content should be saveable"
+ assert r.valid?, "A reply with content should be savable"
assert r.after_validation_performed, "after_validation callback should be called"
end
@@ -213,7 +213,7 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0]
end
- def test_validaton_with_if_and_on
+ def test_validation_with_if_and_on
Topic.validates_presence_of :title, :if => Proc.new{|x| x.author_name = "bad"; true }, :on => :update
t = Topic.new(:title => "")
@@ -361,7 +361,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_dup_validity_is_independent
Topic.validates_presence_of :title
- topic = Topic.new("title" => "Litterature")
+ topic = Topic.new("title" => "Literature")
topic.valid?
duped = topic.dup
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 49f7b03464..0c8c8c006e 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,12 +1,188 @@
## Rails 4.0.0 (unreleased) ##
+* Allow ActiveRecord::Base.connection_handler to have thread affinity and be
+ settable, this effectively allows ActiveRecord to be used in a multi threaded
+ setup with multiple connections to multiple dbs.
+
+ *Sam Saffron*
+
+* `rename_column` preserves auto_increment in mysql migrations.
+ Fixes #3493.
+
+ *Vipul A M*
+
+* PostgreSQL geometric type point is supported by ActiveRecord. Fixes #7324.
+
+ *Martin Schuerrer*
+
+* Add support for concurrent indexing in PostgreSQL adapter via the
+ `algorithm: :concurrently` option
+
+ add_index(:people, :last_name, algorithm: :concurrently)
+
+ Also adds support for MySQL index algorithms (`COPY`, `INPLACE`,
+ `DEFAULT`) via the `algorithm: :copy` option
+
+ add_index(:people, :last_name, algorithm: :copy) # or :inplace/:default
+
+ *Dan McClain*
+
+* Add support for fulltext and spatial indexes on MySQL tables with MyISAM database
+ engine via the `type: 'FULLTEXT'` / `type: 'SPATIAL'` option
+
+ add_index(:people, :last_name, type: 'FULLTEXT')
+ add_index(:people, :last_name, type: 'SPATIAL')
+
+ *Ken Mazaika*
+
+* Add an `add_index` override in Postgresql adapter and MySQL adapter
+ to allow custom index type support. Fixes #6101.
+
+ add_index(:wikis, :body, :using => 'gin')
+
+ *Stefan Huber* and *Doabit*
+
+* After extraction of mass-assignment attributes (which protects [id, type]
+ by default) we can pass id to `update_attributes` and it will update
+ another record because id will be used in where statement. We never have
+ to change id in where statement because we try to set/replace fields for
+ already loaded record but we have to try to set new id for that record.
+
+ *Dmitry Vorotilin*
+
+* Models with multiple counter cache associations now update correctly on destroy.
+ See #7706.
+
+ *Ian Young*
+
+* If `:inverse_of` is true on an association, then when one calls `find()` on
+ the association, Active Record will first look through the in-memory objects
+ in the association for a particular id. Then, it will go to the DB if it
+ is not found. This is accomplished by calling `find_by_scan` in
+ collection associations whenever `options[:inverse_of]` is not nil.
+
+ Fixes #9470.
+
+ *John Wang*
+
+* `rake db:create` does not change permissions of the MySQL root user.
+ Fixes #8079.
+
+ *Yves Senn*
+
+* The length of the `version` column in the `schema_migrations` table
+ created by the `mysql2` adapter is 191 if the encoding is "utf8mb4".
+
+ The "utf8" encoding in MySQL has support for a maximum of 3 bytes per character,
+ and only contains characters from the BMP. The recently added
+ [utf8mb4](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)
+ encoding extends the support to four bytes. As of this writing, said encoding
+ is supported in the betas of the `mysql2` gem.
+
+ Setting the encoding to "utf8mb4" has
+ [a few implications](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html).
+ This change addresses the max length for indexes, which is 191 instead of 255.
+
+ *Xavier Noria*
+
+* Counter caches on associations will now stay valid when attributes are
+ updated (not just when records are created or destroyed), for example,
+ when calling `update_attributes`. The following code now works:
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post, counter_cache: true
+ end
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ post = Post.create
+ comment = Comment.create
+
+ post.comments << comment
+ post.save.reload.comments_count # => 1
+ comment.update_attributes(post_id: nil)
+
+ post.save.reload.comments_count # => 0
+
+ Updating the id of a `belongs_to` object with the id of a new object will
+ also keep the count accurate.
+
+ *John Wang*
+
+* Referencing join tables implicitly was deprecated. There is a
+ possibility that these deprecation warnings are shown even if you
+ don't make use of that feature. You can now disable the feature entirely.
+ Fixes #9712.
+
+ Example:
+
+ # in your configuration
+ config.active_record.disable_implicit_join_references = true
+
+ # or directly
+ ActiveRecord::Base.disable_implicit_join_references = true
+
+ *Yves Senn*
+
+* The `:distinct` option for `Relation#count` is deprecated. You
+ should use `Relation#distinct` instead.
+
+ Example:
+
+ # Before
+ Post.select(:author_name).count(distinct: true)
+
+ # After
+ Post.select(:author_name).distinct.count
+
+ *Yves Senn*
+
+* Rename `Relation#uniq` to `Relation#distinct`. `#uniq` is still
+ available as an alias but we encourage to use `#distinct` instead.
+ Also `Relation#uniq_value` is aliased to `Relation#distinct_value`,
+ this is a temporary solution and you should migrate to `distinct_value`.
+
+ *Yves Senn*
+
+* Fix quoting for sqlite migrations using `copy_table_contents` with binary
+ columns.
+
+ These would fail with "SQLite3::SQLException: unrecognized token" because
+ the column was not being passed to `quote` so the data was not quoted
+ correctly.
+
+ *Matthew M. Boedicker*
+
+* Promotes `change_column_null` to the migrations API. This macro sets/removes
+ `NOT NULL` constraints, and accepts an optional argument to replace existing
+ `NULL`s if needed. The adapters for SQLite, MySQL, PostgreSQL, and (at least)
+ Oracle, already implement this method.
+
+ *Xavier Noria*
+
+* Uniqueness validation allows you to pass `:conditions` to limit
+ the constraint lookup.
+
+ Example:
+
+ validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) }
+
+ *Mattias Pfeiffer + Yves Senn*
+
+* `connection` is deprecated as an instance method.
+ This allows end-users to have a `connection` method on their models
+ without clashing with Active Record internals.
+
+ *Ben Moss*
+
* When copying migrations, preserve their magic comments and content encoding.
*OZAWA Sakuro*
-* Fix ActiveRecord `subclass_from_attrs` when `eager_load` is false.
- It cannot find subclass because all classes are loaded automatically
- when it needs.
+* Fix `subclass_from_attrs` when `eager_load` is false. It cannot find
+ subclass because all classes are loaded automatically when it needs.
*Dmitry Vorotilin*
@@ -95,7 +271,7 @@
*Aaron Weiner*
-* Warn when `rake db:structure:dump` with a mysl database and
+* Warn when `rake db:structure:dump` with a MySQL database and
`mysqldump` is not in the PATH or fails.
Fixes #9518.
@@ -120,7 +296,7 @@
*Yves Senn*
* Assigning "0.0" to a nullable numeric column does not make it dirty.
- Fix #9034.
+ Fixes #9034.
Example:
@@ -147,7 +323,7 @@
*John Wang*
-* Postgresql timestamp with time zone (timestamptz) datatype now returns a
+* PostgreSQL timestamp with time zone (timestamptz) datatype now returns a
ActiveSupport::TimeWithZone instance instead of a string
*Troy Kruthoff*
@@ -377,7 +553,7 @@
*James Miller*
-* Allow store accessors to be overrided like other attribute methods, e.g.:
+* Allow store accessors to be overridden like other attribute methods, e.g.:
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ], coder: JSON
@@ -400,11 +576,11 @@
*Dylan Smith*
* Schema dumper supports dumping the enabled database extensions to `schema.rb`
- (currently only supported by postgresql).
+ (currently only supported by PostgreSQL).
*Justin George*
-* The database adpters now converts the options passed thought `DATABASE_URL`
+* The database adapters now converts the options passed thought `DATABASE_URL`
environment variable to the proper Ruby types before using. For example, SQLite requires
that the timeout value is an integer, and PostgreSQL requires that the
prepared_statements option is a boolean. These now work as expected:
@@ -503,7 +679,7 @@
*John Wang*
-* Collection associations `#empty?` always respects builded records.
+* Collection associations `#empty?` always respects built records.
Fixes #8879.
Example:
@@ -555,17 +731,17 @@
*Marc-André Lafortune*
* Serialized attributes can be serialized in integer columns.
- Fix #8575.
+ Fixes #8575.
*Rafael Mendonça França*
* Keep index names when using `alter_table` with sqlite3.
- Fix #3489.
+ Fixes #3489.
*Yves Senn*
-* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`.
- Fix #5523.
+* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`.
+ Fixes #5523.
*Gary S. Weaver*
@@ -588,7 +764,7 @@
*Matthew Robertson*
* Recognize migrations placed in directories containing numbers and 'rb'.
- Fix #8492
+ Fixes #8492.
*Yves Senn*
@@ -646,13 +822,13 @@
* Fix performance problem with `primary_key` method in PostgreSQL adapter when having many schemas.
Uses `pg_constraint` table instead of `pg_depend` table which has many records in general.
- Fix #8414
+ Fixes #8414.
*kennyj*
* Do not instantiate intermediate Active Record objects when eager loading.
These records caused `after_find` to run more than expected.
- Fix #3313
+ Fixes #3313.
*Yves Senn*
@@ -673,12 +849,13 @@
* Fix dirty attribute checks for `TimeZoneConversion` with nil and blank
datetime attributes. Setting a nil datetime to a blank string should not
- result in a change being flagged. Fix #8310
+ result in a change being flagged.
+ Fixes #8310.
*Alisdair McDiarmid*
* Prevent mass assignment to the type column of polymorphic associations when using `build`
- Fix #8265
+ Fixes #8265.
*Yves Senn*
@@ -687,14 +864,14 @@
*Carlos Antonio da Silva*
-* Fix postgresql adapter to handle BC timestamps correctly
+* Fix PostgreSQL adapter to handle BC timestamps correctly
HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years)
*Bogdan Gusiev*
-* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped.
- Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types.
+* When running migrations on PostgreSQL, the `:limit` option for `binary` and `text` columns is silently dropped.
+ Previously, these migrations caused sql exceptions, because PostgreSQL doesn't support limits on these types.
*Victor Costan*
@@ -731,7 +908,7 @@
*Bogdan Gusiev*
* `:counter_cache` option for `has_many` associations to support custom named counter caches.
- Fix #7993
+ Fixes #7993.
*Yves Senn*
@@ -755,7 +932,7 @@
*Nikita Afanasenko*
* Use query cache/uncache when using `DATABASE_URL`.
- Fix #6951.
+ Fixes #6951.
*kennyj*
@@ -764,7 +941,7 @@
*Henrik Nyh*
* The `create_table` method raises an `ArgumentError` when the primary key column is redefined.
- Fix #6378
+ Fixes #6378.
*Yves Senn*
@@ -874,7 +1051,7 @@
*Alexey Muranov*
* The postgres adapter now supports tables with capital letters.
- Fix #5920
+ Fixes #5920.
*Yves Senn*
@@ -896,7 +1073,7 @@
*Francesco Rodriguez*
* Fix `reset_counters` crashing on `has_many :through` associations.
- Fix #7822.
+ Fixes #7822.
*lulalala*
@@ -971,7 +1148,7 @@
*Guillermo Iguaran*
* Fix the return of querying with an empty hash.
- Fix #6971.
+ Fixes #6971.
User.where(token: {})
@@ -987,7 +1164,7 @@
* Fix creation of through association models when using `collection=[]`
on a `has_many :through` association from an unsaved model.
- Fix #7661.
+ Fixes #7661.
*Ernie Miller*
@@ -1326,7 +1503,7 @@
*Egor Lynko*
-* Added support for specifying the precision of a timestamp in the postgresql
+* Added support for specifying the precision of a timestamp in the PostgreSQL
adapter. So, instead of having to incorrectly specify the precision using the
`:limit` option, you may use `:precision`, as intended. For example, in a migration:
@@ -1381,7 +1558,7 @@
* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of
this is to move when the exceptions are raised from the point of declaration
- to when the association is built. This is consistant with other association
+ to when the association is built. This is consistent with other association
validity checks.
*Andrew White*
@@ -1594,7 +1771,7 @@
* Added the schema cache dump feature.
- `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
+ `Schema cache dump` feature was implemented. This feature can dump/load internal state of `SchemaCache` instance
because we want to boot rails more quickly when we have many models.
Usage notes:
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 89a62f0873..3e3475f709 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,6 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 4.0.0.beta1'
+ s.add_dependency 'arel', '~> 4.0.0.beta2'
s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3'
end
diff --git a/activerecord/examples/associations.png b/activerecord/examples/associations.png
deleted file mode 100644
index 661c7a8bbc..0000000000
--- a/activerecord/examples/associations.png
+++ /dev/null
Binary files differ
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5613c54f0b..4fd817bd8c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -241,6 +241,7 @@ module ActiveRecord
# others.destroy_all | X | X | X
# others.find(*args) | X | X | X
# others.exists? | X | X | X
+ # others.distinct | X | X | X
# others.uniq | X | X | X
# others.reset | X | X | X
#
@@ -965,7 +966,7 @@ module ActiveRecord
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
# cause the records in the join table to be removed.
#
- # For +has_many+, <tt>destroy</tt> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the
+ # For +has_many+, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
@@ -1024,7 +1025,7 @@ module ActiveRecord
# [collection<<(object, ...)]
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
# Note that this operation instantly fires update sql without waiting for the save or update call on the
- # parent object.
+ # parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
@@ -1407,6 +1408,8 @@ module ActiveRecord
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
# custom <tt>:join_table</tt> option if you need to.
+ # If your tables share a common prefix, it will only appear once at the beginning. For example,
+ # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
#
# The join table should not have a primary key or a model associated with it. You must manually generate the
# join table with a migration such as this:
@@ -1433,7 +1436,7 @@ module ActiveRecord
# Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
# Note that this operation instantly fires update sql without waiting for the save or update call on the
- # parent object.
+ # parent object, unless the parent object is a new record.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by removing their associations from the join table.
# This does not destroy the objects.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index a5c8d6cf77..f3f4792eaa 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -92,7 +92,7 @@ module ActiveRecord
# The scope for this association.
#
# Note that the association_scope is merged into the target_scope only when the
- # scoped method is called. This is because at that point the call may be surrounded
+ # scope method is called. This is because at that point the call may be surrounded
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
# actually gets built.
def association_scope
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # This class of the target. belongs_to polymorphic overrides this to look at the
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
reflection.klass
@@ -203,7 +203,7 @@ module ActiveRecord
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
# the kind of the class of the associated objects. Meant to be used as
# a sanity check when you are about to assign an associated record.
- def raise_on_type_mismatch(record)
+ def raise_on_type_mismatch!(record)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index c5fb1fe2c7..a9525436fb 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -22,7 +22,7 @@ module ActiveRecord
private
def column_for(table_name, column_name)
- columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
+ columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
columns[column_name]
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 54b1a69774..8eec4f56af 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def replace(record)
- raise_on_type_mismatch(record) if record
+ raise_on_type_mismatch!(record) if record
update_counters(record)
replace_keys(record)
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 88ce03a3cd..eae5eed3a1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
reflection.polymorphic_inverse_of(record.class)
end
- def raise_on_type_mismatch(record)
+ def raise_on_type_mismatch!(record)
# A polymorphic association cannot have a type mismatch, by definition
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 97b1ff18e2..9ac561b997 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -21,23 +21,43 @@ module ActiveRecord::Associations::Builder
def add_counter_cache_callbacks(reflection)
cache_column = reflection.counter_cache_column
+ foreign_key = reflection.foreign_key
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_counter_cache_after_create_for_#{name}
record = #{name}
record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
+ @_after_create_counter_called = true
end
def belongs_to_counter_cache_before_destroy_for_#{name}
- unless marked_for_destruction?
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
record = #{name}
record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
end
end
+
+ def belongs_to_counter_cache_after_update_for_#{name}
+ if (@_after_create_counter_called ||= false)
+ @_after_create_counter_called = false
+ elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize})
+ model = #{name.to_s.camelize}
+ foreign_key_was = self.#{foreign_key}_was
+ foreign_key = self.#{foreign_key}
+
+ if foreign_key && model.respond_to?(:increment_counter)
+ model.increment_counter(:#{cache_column}, foreign_key)
+ end
+ if foreign_key_was && model.respond_to?(:decrement_counter)
+ model.decrement_counter(:#{cache_column}, foreign_key_was)
+ end
+ end
+ end
CODE
model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
+ model.after_update "belongs_to_counter_cache_after_update_for_#{name}"
klass = reflection.class_name.safe_constantize
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 5feb149946..2385c90c1a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -34,7 +34,7 @@ module ActiveRecord
reload
end
- CollectionProxy.new(klass, self)
+ @proxy ||= CollectionProxy.new(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -79,7 +79,7 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql]
+ if options[:finder_sql] || options[:inverse_of]
find_by_scan(*args)
else
scope.find(*args)
@@ -174,13 +174,14 @@ module ActiveRecord
reflection.klass.count_by_sql(custom_counter_sql)
else
- if association_scope.uniq_value
+ relation = scope
+ if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name ||= reflection.klass.primary_key
- count_options[:distinct] = true
+ relation = relation.distinct
end
- value = scope.count(column_name, count_options)
+ value = relation.count(column_name)
limit = options[:limit]
offset = options[:offset]
@@ -204,6 +205,15 @@ module ActiveRecord
dependent = options[:dependent]
if records.first == :all
+
+ if dependent && dependent == :destroy
+ message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
+ 'It means if the :dependent option is :destroy then the associated ' \
+ 'records would be deleted without loading and invoking callbacks.'
+
+ ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
+ end
+
if loaded? || dependent == :destroy
delete_or_destroy(load_target, dependent)
else
@@ -237,14 +247,14 @@ module ActiveRecord
# +count_records+, which is a method descendants have to provide.
def size
if !find_target? || loaded?
- if association_scope.uniq_value
+ if association_scope.distinct_value
target.uniq.size
else
target.size
end
elsif !loaded? && !association_scope.group_values.empty?
load_target.size
- elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array)
+ elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
unsaved_records = target.select { |r| r.new_record? }
unsaved_records.size + count_records
else
@@ -297,17 +307,18 @@ module ActiveRecord
end
end
- def uniq
+ def distinct
seen = {}
load_target.find_all do |record|
seen[record.id] = true unless seen.key?(record.id)
end
end
+ alias uniq distinct
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
def replace(other_array)
- other_array.each { |val| raise_on_type_mismatch(val) }
+ other_array.each { |val| raise_on_type_mismatch!(val) }
original_target = load_target.dup
if owner.new_record?
@@ -343,7 +354,7 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
- if association_scope.uniq_value && index = @target.index(record)
+ if association_scope.distinct_value && index = @target.index(record)
@target[index] = record
else
@target << record
@@ -454,7 +465,7 @@ module ActiveRecord
def delete_or_destroy(records, method)
records = records.flatten
- records.each { |record| raise_on_type_mismatch(record) }
+ records.each { |record| raise_on_type_mismatch!(record) }
existing_records = records.reject { |r| r.new_record? }
if existing_records.empty?
@@ -495,9 +506,9 @@ module ActiveRecord
result = true
records.flatten.each do |record|
- raise_on_type_mismatch(record)
- add_to_target(record) do |r|
- result &&= insert_record(record) unless owner.new_record?
+ raise_on_type_mismatch!(record)
+ add_to_target(record) do |rec|
+ result &&= insert_record(rec) unless owner.new_record?
end
end
@@ -556,7 +567,8 @@ module ActiveRecord
end
end
- # If using a custom finder_sql, #find scans the entire collection.
+ # If using a custom finder_sql or if the :inverse_of option has been
+ # specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 543204abac..c2add32aa6 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -649,11 +649,12 @@ module ActiveRecord
# # #<Pet name: "Fancy-Fancy">
# # ]
#
- # person.pets.select(:name).uniq
+ # person.pets.select(:name).distinct
# # => [#<Pet name: "Fancy-Fancy">]
- def uniq
- @association.uniq
+ def distinct
+ @association.distinct
end
+ alias uniq distinct
# Count all records using SQL.
#
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 93618721bb..bb3e3db379 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -26,7 +26,7 @@ module ActiveRecord
join_table[reflection.association_foreign_key] => record.id
)
- owner.connection.insert stmt
+ owner.class.connection.insert stmt
end
record
@@ -41,7 +41,7 @@ module ActiveRecord
def delete_records(records, method)
if sql = options[:delete_sql]
records = load_target if records == :all
- records.each { |record| owner.connection.delete(interpolate(sql, record)) }
+ records.each { |record| owner.class.connection.delete(interpolate(sql, record)) }
else
relation = join_table
condition = relation[reflection.foreign_key].eq(owner.id)
@@ -53,7 +53,7 @@ module ActiveRecord
)
end
- owner.connection.delete(relation.where(condition).compile_delete)
+ owner.class.connection.delete(relation.where(condition).compile_delete)
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f59565ae77..29fae809da 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -22,10 +22,11 @@ module ActiveRecord
else
if options[:dependent] == :destroy
# No point in executing the counter update since we're going to destroy the parent anyway
- load_target.each(&:mark_for_destruction)
+ load_target.each { |t| t.destroyed_by_association = reflection }
+ destroy_all
+ else
+ delete_all
end
-
- delete_all
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index d1458f30ba..a74dd1cdab 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
- raise_on_type_mismatch(record)
+ raise_on_type_mismatch!(record)
record.save! if record.new_record?
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index ee816d2392..98bd010f70 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def replace(record, save = true)
- raise_on_type_mismatch(record) if record
+ raise_on_type_mismatch!(record) if record
load_target
# If target and record are nil, or target is equal to record,
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index 8e8925f0a9..9a3fada380 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -35,7 +35,7 @@ module ActiveRecord
# record
def associated_records_by_owner
records = {}
- super.each do |owner_key, rows|
+ super.each_value do |rows|
rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 9a662d3f53..38bc7ce7da 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def associated_records_by_owner
super.each do |owner, records|
- records.uniq! if reflection_scope.uniq_value
+ records.uniq! if reflection_scope.distinct_value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ecfa556ab4..e536f5ebcc 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def extract_callstack_for_multiparameter_attributes(pairs)
- attributes = { }
+ attributes = {}
pairs.each do |(multiparameter_name, value)|
attribute_name = multiparameter_name.split("(").first
@@ -146,7 +146,7 @@ module ActiveRecord
end
else
# else column is a timestamp, so if Date bits were not provided, error
- validate_missing_parameters!([1,2,3])
+ validate_required_parameters!([1,2,3])
# If Date bits were provided but blank, then return nil
return if blank_date_parameter?
@@ -172,14 +172,14 @@ module ActiveRecord
def read_other(klass)
max_position = extract_max_param
positions = (1..max_position)
- validate_missing_parameters!(positions)
+ validate_required_parameters!(positions)
set_values = values.values_at(*positions)
klass.new(*set_values)
end
# Checks whether some blank date parameter exists. Note that this is different
- # than the validate_missing_parameters! method, since it just checks for blank
+ # than the validate_required_parameters! method, since it just checks for blank
# positions instead of missing ones, and does not raise in case one blank position
# exists. The caller is responsible to handle the case of this returning true.
def blank_date_parameter?
@@ -187,7 +187,7 @@ module ActiveRecord
end
# If some position is not provided, it errors out a missing parameter exception.
- def validate_missing_parameters!(positions)
+ def validate_required_parameters!(positions)
if missing_parameter = positions.detect { |position| !values.key?(position) }
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e0bfdb8f3e..22405c5d74 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods # :nodoc:
- # Use a mutex; we don't want two thread simaltaneously trying to define
+ # Use a mutex; we don't want two thread simultaneously trying to define
# attribute methods.
@attribute_methods_mutex.synchronize do
return if attribute_methods_generated?
@@ -334,7 +334,7 @@ module ActiveRecord
private
# Returns a Hash of the Arel::Attributes and attribute values that have been
- # type casted for use in an Arel insert/update method.
+ # typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
attrs = {}
arel_table = self.class.arel_table
@@ -348,7 +348,7 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.select do |name|
- column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
+ column_for_attribute(name) && !readonly_attribute?(name)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 3e454b713a..931209b07b 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -90,7 +90,7 @@ module ActiveRecord
base_name.foreign_key
else
if ActiveRecord::Base != self && table_exists?
- connection.schema_cache.primary_keys[table_name]
+ connection.schema_cache.primary_keys(table_name)
else
'id'
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 55542262b0..0df3e57947 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -212,6 +212,7 @@ module ActiveRecord
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
def reload(options = nil)
@marked_for_destruction = false
+ @destroyed_by_association = nil
super
end
@@ -231,6 +232,19 @@ module ActiveRecord
@marked_for_destruction
end
+ # Records the association that is being destroyed and destroying this
+ # record in the process.
+ def destroyed_by_association=(reflection)
+ @destroyed_by_association = reflection
+ end
+
+ # Returns the association for the parent being destroyed.
+ #
+ # Used to avoid updating the counter cache unnecessarily.
+ def destroyed_by_association
+ @destroyed_by_association
+ end
+
# Returns whether or not this record has been changed in any way (including whether
# any of its nested autosave associations are likewise changed)
def changed_for_autosave?
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f6cdc67b4d..8d22942a06 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -3,7 +3,6 @@ require 'yaml'
module ActiveRecord
module Coders # :nodoc:
class YAMLColumn # :nodoc:
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
@@ -34,7 +33,7 @@ module ActiveRecord
obj ||= object_class.new if object_class != Object
obj
- rescue *RESCUE_ERRORS
+ rescue ArgumentError, Psych::SyntaxError
yaml
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 1754e424b8..bf2f945448 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -406,7 +406,9 @@ module ActiveRecord
synchronize do
stale = Time.now - @dead_connection_timeout
connections.dup.each do |conn|
- remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
+ if conn.in_use? && stale > conn.last_use && !conn.active?
+ remove conn
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 2859fb31e8..c0a2111571 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# limit is enforced by rails and Is less than or equal to
# <tt>index_name_length</tt>. The gap between
# <tt>index_name_length</tt> is to allow internal rails
- # opreations to use prefixes in temporary opreations.
+ # operations to use prefixes in temporary operations.
def allowed_index_name_length
index_name_length
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 42206de8fc..eb974e4a6e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -8,37 +8,21 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
-
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc:
def string_to_binary(value)
value
end
- def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale)
- end
-
- def to_sql
- column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- column_options = {}
- column_options[:null] = null unless null.nil?
- column_options[:default] = default unless default.nil?
- add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
- column_sql
+ def primary_key?
+ primary_key || type.to_sym == :primary_key
end
-
- private
-
- def add_column_options!(sql, options)
- base.add_column_options!(sql, options.merge(:column => self))
- end
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -64,28 +48,25 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns, :indexes
+ attr_accessor :indexes
+ attr_reader :name, :temporary, :options
- def initialize(base)
- @columns = []
+ def initialize(types, name, temporary, options)
@columns_hash = {}
@indexes = {}
- @base = base
+ @native = types
+ @temporary = temporary
+ @options = options
+ @name = name
end
- def xml(*args)
- raise NotImplementedError unless %w{
- sqlite mysql mysql2
- }.include? @base.adapter_name.downcase
-
- options = args.extract_options!
- column(args[0], :text, options)
- end
+ def columns; @columns_hash.values; end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
- def primary_key(name)
- column(name, :primary_key)
+ def primary_key(name, type = :primary_key, options = {})
+ options[:primary_key] = true
+ column(name, type, options)
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -238,20 +219,14 @@ module ActiveRecord
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
- column = self[name] || new_column_definition(@base, name, type)
-
- limit = options.fetch(:limit) do
- native[type][:limit] if native[type].is_a?(Hash)
- end
-
- column.limit = limit
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
+ @columns_hash[name] = new_column_definition(name, type, options)
self
end
+ def remove_column(name)
+ @columns_hash.delete name.to_s
+ end
+
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -288,28 +263,52 @@ module ActiveRecord
end
alias :belongs_to :references
- # Returns a String whose contents are the column definitions
- # concatenated together. This string can then be prepended and appended to
- # to generate the final SQL to create the table.
- def to_sql
- @columns.map { |c| c.to_sql } * ', '
+ def new_column_definition(name, type, options) # :nodoc:
+ column = create_column_definition name, type
+ limit = options.fetch(:limit) do
+ native[type][:limit] if native[type].is_a?(Hash)
+ end
+
+ column.limit = limit
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ column.first = options[:first]
+ column.after = options[:after]
+ column.primary_key = type == :primary_key || options[:primary_key]
+ column
end
private
- def new_column_definition(base, name, type)
- definition = ColumnDefinition.new base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
def primary_key_column_name
- primary_key_column = columns.detect { |c| c.type == :primary_key }
+ primary_key_column = columns.detect { |c| c.primary_key? }
primary_key_column && primary_key_column.name
end
def native
- @base.native_database_types
+ @native
+ end
+ end
+
+ class AlterTable # :nodoc:
+ attr_reader :adds
+
+ def initialize(td)
+ @td = td
+ @adds = []
+ end
+
+ def name; @td.name; end
+
+ def add_column(name, type, options)
+ name = name.to_s
+ type = type.to_sym
+ @adds << @td.new_column_definition(name, type, options)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index f587bf8140..cdf0cbe218 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# The goal of this module is to move Adapter specific column
# definitions to the Adapter instead of having it in the schema
# dumper itself. This code represents the normal case.
- # We can then redefine how certain data types may be handled in the schema dumper on the
+ # We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column, types)
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 c1f3462bbe..aebcc2d874 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module SchemaStatements
include ActiveRecord::Migration::JoinTable
- # Returns a Hash of mappings from the abstract data types to the native
+ # Returns a hash of mappings from the abstract data types to the native
# database types. See TableDefinition#column for details on the recognized
# abstract data types.
def native_database_types
@@ -20,6 +20,7 @@ module ActiveRecord
# Checks to see if the table +table_name+ exists on the database.
#
# table_exists?(:developers)
+ #
def table_exists?(table_name)
tables.include?(table_name.to_s)
end
@@ -29,17 +30,18 @@ module ActiveRecord
# Checks to see if an index exists on a table for a given index definition.
#
- # # Check an index exists
- # index_exists?(:suppliers, :company_id)
+ # # Check an index exists
+ # index_exists?(:suppliers, :company_id)
+ #
+ # # Check an index on multiple columns exists
+ # index_exists?(:suppliers, [:company_id, :company_type])
#
- # # Check an index on multiple columns exists
- # index_exists?(:suppliers, [:company_id, :company_type])
+ # # Check a unique index exists
+ # index_exists?(:suppliers, :company_id, unique: true)
#
- # # Check a unique index exists
- # index_exists?(:suppliers, :company_id, unique: true)
+ # # Check an index with a custom name exists
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id"
#
- # # Check an index with a custom name exists
- # index_exists?(:suppliers, :company_id, name: "idx_company_id")
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name)
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
@@ -56,17 +58,18 @@ module ActiveRecord
# Checks to see if a column exists in a given table.
#
- # # Check a column exists
- # column_exists?(:suppliers, :name)
+ # # Check a column exists
+ # column_exists?(:suppliers, :name)
#
- # # Check a column exists of a particular type
- # column_exists?(:suppliers, :name, :string)
+ # # Check a column exists of a particular type
+ # column_exists?(:suppliers, :name, :string)
+ #
+ # # Check a column exists with a specific definition
+ # column_exists?(:suppliers, :name, :string, limit: 100)
+ # column_exists?(:suppliers, :name, :string, default: 'default')
+ # column_exists?(:suppliers, :name, :string, null: false)
+ # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
#
- # # Check a column exists with a specific definition
- # column_exists?(:suppliers, :name, :string, limit: 100)
- # column_exists?(:suppliers, :name, :string, default: 'default')
- # column_exists?(:suppliers, :name, :string, null: false)
- # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
def column_exists?(table_name, column_name, type = nil, options = {})
columns(table_name).any?{ |c| c.name == column_name.to_s &&
(!type || c.type == type) &&
@@ -84,27 +87,30 @@ module ActiveRecord
# form or the regular form, like this:
#
# === Block form
- # # create_table() passes a TableDefinition object to the block.
- # # This form will not only create the table, but also columns for the
- # # table.
#
- # create_table(:suppliers) do |t|
- # t.column :name, :string, limit: 60
- # # Other fields here
- # end
+ # # create_table() passes a TableDefinition object to the block.
+ # # This form will not only create the table, but also columns for the
+ # # table.
+ #
+ # create_table(:suppliers) do |t|
+ # t.column :name, :string, limit: 60
+ # # Other fields here
+ # end
#
# === Block form, with shorthand
- # # You can also use the column types as method calls, rather than calling the column method.
- # create_table(:suppliers) do |t|
- # t.string :name, limit: 60
- # # Other fields here
- # end
+ #
+ # # You can also use the column types as method calls, rather than calling the column method.
+ # create_table(:suppliers) do |t|
+ # t.string :name, limit: 60
+ # # Other fields here
+ # end
#
# === Regular form
- # # Creates a table called 'suppliers' with no columns.
- # create_table(:suppliers)
- # # Add a column to 'suppliers'.
- # add_column(:suppliers, :name, :string, {limit: 60})
+ #
+ # # Creates a table called 'suppliers' with no columns.
+ # create_table(:suppliers)
+ # # Add a column to 'suppliers'.
+ # add_column(:suppliers, :name, :string, {limit: 60})
#
# The +options+ hash can include the following keys:
# [<tt>:id</tt>]
@@ -127,37 +133,53 @@ module ActiveRecord
# Defaults to false.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
- # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ #
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ #
# generates:
- # CREATE TABLE suppliers (
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ #
+ # CREATE TABLE suppliers (
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
#
# ====== Rename the primary key column
- # create_table(:objects, primary_key: 'guid') do |t|
- # t.column :name, :string, limit: 80
- # end
+ #
+ # create_table(:objects, primary_key: 'guid') do |t|
+ # t.column :name, :string, limit: 80
+ # end
+ #
# generates:
- # CREATE TABLE objects (
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
- # name varchar(80)
- # )
+ #
+ # CREATE TABLE objects (
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ # name varchar(80)
+ # )
#
# ====== Do not add a primary key column
- # create_table(:categories_suppliers, id: false) do |t|
- # t.column :category_id, :integer
- # t.column :supplier_id, :integer
- # end
+ #
+ # create_table(:categories_suppliers, id: false) do |t|
+ # t.column :category_id, :integer
+ # t.column :supplier_id, :integer
+ # end
+ #
# generates:
- # CREATE TABLE categories_suppliers (
- # category_id int,
- # supplier_id int
- # )
+ #
+ # CREATE TABLE categories_suppliers (
+ # category_id int,
+ # supplier_id int
+ # )
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition
- td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
+ td = create_table_definition table_name, options[:temporary], options[:options]
+
+ 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
yield td if block_given?
@@ -165,19 +187,15 @@ module ActiveRecord
drop_table(table_name, options)
end
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
- create_sql << "#{quote_table_name(table_name)} ("
- create_sql << td.to_sql
- create_sql << ") #{options[:options]}"
- execute create_sql
+ execute schema_creation.accept td
td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
# Creates a new join table with the name created using the lexical order of the first two
# arguments. These arguments can be a String or a Symbol.
#
- # # Creates a table called 'assemblies_parts' with no id.
- # create_join_table(:assemblies, :parts)
+ # # Creates a table called 'assemblies_parts' with no id.
+ # create_join_table(:assemblies, :parts)
#
# You can pass a +options+ hash can include the following keys:
# [<tt>:table_name</tt>]
@@ -201,12 +219,16 @@ module ActiveRecord
# end
#
# ====== Add a backend specific option to the generated SQL (MySQL)
- # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ #
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ #
# generates:
- # CREATE TABLE assemblies_parts (
- # assembly_id int NOT NULL,
- # part_id int NOT NULL,
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ #
+ # CREATE TABLE assemblies_parts (
+ # assembly_id int NOT NULL,
+ # part_id int NOT NULL,
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ #
def create_join_table(table_1, table_2, options = {})
join_table_name = find_join_table_name(table_1, table_2, options)
@@ -223,7 +245,7 @@ module ActiveRecord
end
# Drops the join table specified by the given arguments.
- # See create_join_table for details.
+ # See +create_join_table+ for details.
#
# Although this command ignores the block if one is given, it can be helpful
# to provide one in a migration's +change+ method so it can be reverted.
@@ -235,66 +257,74 @@ module ActiveRecord
# A block for changing columns in +table+.
#
- # # change_table() yields a Table instance
- # change_table(:suppliers) do |t|
- # t.column :name, :string, limit: 60
- # # Other column alterations here
- # end
+ # # change_table() yields a Table instance
+ # change_table(:suppliers) do |t|
+ # t.column :name, :string, limit: 60
+ # # Other column alterations here
+ # end
#
# The +options+ hash can include the following keys:
# [<tt>:bulk</tt>]
# Set this to true to make this a bulk alter query, such as
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
+ #
+ # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
#
# Defaults to false.
#
# ====== Add a column
- # change_table(:suppliers) do |t|
- # t.column :name, :string, limit: 60
- # end
+ #
+ # change_table(:suppliers) do |t|
+ # t.column :name, :string, limit: 60
+ # end
#
# ====== Add 2 integer columns
- # change_table(:suppliers) do |t|
- # t.integer :width, :height, null: false, default: 0
- # end
+ #
+ # change_table(:suppliers) do |t|
+ # t.integer :width, :height, null: false, default: 0
+ # end
#
# ====== Add created_at/updated_at columns
- # change_table(:suppliers) do |t|
- # t.timestamps
- # end
+ #
+ # change_table(:suppliers) do |t|
+ # t.timestamps
+ # end
#
# ====== Add a foreign key column
- # change_table(:suppliers) do |t|
- # t.references :company
- # end
#
- # Creates a <tt>company_id(integer)</tt> column
+ # change_table(:suppliers) do |t|
+ # t.references :company
+ # end
+ #
+ # Creates a <tt>company_id(integer)</tt> column.
#
# ====== Add a polymorphic foreign key column
+ #
# change_table(:suppliers) do |t|
# t.belongs_to :company, polymorphic: true
# end
#
- # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
#
# ====== Remove a column
+ #
# change_table(:suppliers) do |t|
# t.remove :company
# end
#
# ====== Remove several columns
+ #
# change_table(:suppliers) do |t|
# t.remove :company_id
# t.remove :width, :height
# end
#
# ====== Remove an index
+ #
# change_table(:suppliers) do |t|
# t.remove_index :company_id
# end
#
- # See also Table for details on
- # all of the various column transformation
+ # See also Table for details on all of the various column transformation.
def change_table(table_name, options = {})
if supports_bulk_alter? && options[:bulk]
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
@@ -307,7 +337,8 @@ module ActiveRecord
# Renames a table.
#
- # rename_table('octopuses', 'octopi')
+ # rename_table('octopuses', 'octopi')
+ #
def rename_table(table_name, new_name)
raise NotImplementedError, "rename_table is not implemented"
end
@@ -324,14 +355,15 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- execute(add_column_sql)
+ at = create_alter_table table_name
+ at.add_column(column_name, type, options)
+ execute schema_creation.accept at
end
# Removes the given columns from the table definition.
#
- # remove_columns(:suppliers, :qualification, :experience)
+ # remove_columns(:suppliers, :qualification, :experience)
+ #
def remove_columns(table_name, *column_names)
raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
column_names.each do |column_name|
@@ -341,7 +373,7 @@ module ActiveRecord
# Removes the column from the table definition.
#
- # remove_column(:suppliers, :qualification)
+ # remove_column(:suppliers, :qualification)
#
# The +type+ and +options+ parameters will be ignored if present. It can be helpful
# to provide these in a migration's +change+ method so it can be reverted.
@@ -353,24 +385,50 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
#
- # change_column(:suppliers, :name, :string, limit: 80)
- # change_column(:accounts, :description, :text)
+ # change_column(:suppliers, :name, :string, limit: 80)
+ # change_column(:accounts, :description, :text)
+ #
def change_column(table_name, column_name, type, options = {})
raise NotImplementedError, "change_column is not implemented"
end
- # Sets a new default value for a column.
+ # Sets a new default value for a column:
+ #
+ # change_column_default(:suppliers, :qualification, 'new')
+ # change_column_default(:accounts, :authorized, 1)
+ #
+ # Setting the default to +nil+ effectively drops the default:
+ #
+ # change_column_default(:users, :email, nil)
#
- # change_column_default(:suppliers, :qualification, 'new')
- # change_column_default(:accounts, :authorized, 1)
- # change_column_default(:users, :email, nil)
def change_column_default(table_name, column_name, default)
raise NotImplementedError, "change_column_default is not implemented"
end
+ # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
+ # indicates whether the value can be +NULL+. For example
+ #
+ # change_column_null(:users, :nickname, false)
+ #
+ # says nicknames cannot be +NULL+ (adds the constraint), whereas
+ #
+ # change_column_null(:users, :nickname, true)
+ #
+ # allows them to be +NULL+ (drops the constraint).
+ #
+ # The method accepts an optional fourth argument to replace existing
+ # +NULL+s with some other value. Use that one when enabling the
+ # constraint if needed, since otherwise those rows would not be valid.
+ #
+ # Please note the fourth argument does not set a column's default.
+ def change_column_null(table_name, column_name, null, default = nil)
+ raise NotImplementedError, "change_column_null is not implemented"
+ end
+
# Renames a column.
#
- # rename_column(:suppliers, :description, :name)
+ # rename_column(:suppliers, :description, :name)
+ #
def rename_column(table_name, column_name, new_column_name)
raise NotImplementedError, "rename_column is not implemented"
end
@@ -382,60 +440,106 @@ module ActiveRecord
# you pass <tt>:name</tt> as an option.
#
# ====== Creating a simple index
- # add_index(:suppliers, :name)
+ #
+ # add_index(:suppliers, :name)
+ #
# generates
- # CREATE INDEX suppliers_name_index ON suppliers(name)
+ #
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
#
# ====== Creating a unique index
- # add_index(:accounts, [:branch_id, :party_id], unique: true)
+ #
+ # add_index(:accounts, [:branch_id, :party_id], unique: true)
+ #
# generates
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
+ #
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
# ====== Creating a named index
- # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
+ #
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
+ #
# generates
+ #
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
# ====== Creating an index with specific key length
- # add_index(:accounts, :name, name: 'by_name', length: 10)
+ #
+ # add_index(:accounts, :name, name: 'by_name', length: 10)
+ #
# generates
- # CREATE INDEX by_name ON accounts(name(10))
#
- # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
+ # CREATE INDEX by_name ON accounts(name(10))
+ #
+ # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
+ #
# generates
- # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
- # Note: SQLite doesn't support index length
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
+ #
+ # Note: SQLite doesn't support index length.
#
# ====== Creating an index with a sort order (desc or asc, asc is the default)
- # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
+ #
+ # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
+ #
# generates
- # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
- # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
+ #
+ # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
#
# ====== Creating a partial index
- # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
+ #
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
+ #
+ # generates
+ #
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
+ #
+ # ====== Creating an index with a specific method
+ #
+ # add_index(:developers, :name, :using => 'btree')
+ #
# generates
- # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
- # Note: only supported by PostgreSQL
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
#
+ # Note: only supported by PostgreSQL and MySQL
+ #
+ # ====== Creating an index with a specific type
+ #
+ # add_index(:developers, :name, :type => :fulltext)
+ #
+ # generates
+ #
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
+ #
+ # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
end
- # Remove the given index from the table.
+ # Removes the given index from the table.
+ #
+ # Removes the +index_accounts_on_column+ in the +accounts+ table.
#
- # Remove the index_accounts_on_column in the accounts table.
# remove_index :accounts, :column
- # Remove the index named index_accounts_on_branch_id in the accounts table.
+ #
+ # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
+ #
# remove_index :accounts, column: :branch_id
- # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
+ #
+ # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
+ #
# remove_index :accounts, column: [:branch_id, :party_id]
- # Remove the index named by_branch_party in the accounts table.
+ #
+ # Removes the index named +by_branch_party+ in the +accounts+ table.
+ #
# remove_index :accounts, name: :by_branch_party
+ #
def remove_index(table_name, options = {})
remove_index!(table_name, index_name_for_remove(table_name, options))
end
@@ -444,10 +548,12 @@ module ActiveRecord
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
end
- # Rename an index.
+ # Renames an index.
+ #
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
#
- # Rename the index_people_on_last_name index to index_users_on_last_name
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
+ #
def rename_index(table_name, old_name, new_name)
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
@@ -470,7 +576,7 @@ module ActiveRecord
end
end
- # Verify the existence of an index with a given name.
+ # Verifies the existence of an index with a given name.
#
# The default argument is returned if the underlying implementation does not define the indexes method,
# as there's no way to determine the correct answer in that case.
@@ -484,13 +590,16 @@ module ActiveRecord
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
#
# ====== Create a user_id column
- # add_reference(:products, :user)
+ #
+ # add_reference(:products, :user)
#
# ====== Create a supplier_id and supplier_type columns
- # add_belongs_to(:products, :supplier, polymorphic: true)
+ #
+ # add_belongs_to(:products, :supplier, polymorphic: true)
#
# ====== Create a supplier_id, supplier_type columns and appropriate index
- # add_reference(:products, :supplier, polymorphic: true, index: true)
+ #
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
#
def add_reference(table_name, ref_name, options = {})
polymorphic = options.delete(:polymorphic)
@@ -505,10 +614,12 @@ module ActiveRecord
# <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
# ====== Remove the reference
- # remove_reference(:products, :user, index: true)
+ #
+ # remove_reference(:products, :user, index: true)
#
# ====== Remove polymorphic reference
- # remove_reference(:products, :supplier, polymorphic: true)
+ #
+ # remove_reference(:products, :supplier, polymorphic: true)
#
def remove_reference(table_name, ref_name, options = {})
remove_column(table_name, "#{ref_name}_id")
@@ -589,27 +700,33 @@ module ActiveRecord
if options[:null] == false
sql << " NOT NULL"
end
+ if options[:auto_increment] == true
+ sql << " AUTO_INCREMENT"
+ end
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
# Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
#
# distinct("posts.id", "posts.created_at desc")
+ #
def distinct(columns, order_by)
"DISTINCT #{columns}"
end
- # Adds timestamps (created_at and updated_at) columns to the named table.
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
+ #
+ # add_timestamps(:suppliers)
#
- # add_timestamps(:suppliers)
def add_timestamps(table_name)
add_column table_name, :created_at, :datetime
add_column table_name, :updated_at, :datetime
end
- # Removes the timestamp columns (created_at and updated_at) from the table definition.
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
#
# remove_timestamps(:suppliers)
+ #
def remove_timestamps(table_name)
remove_column table_name, :updated_at
remove_column table_name, :created_at
@@ -650,12 +767,21 @@ module ActiveRecord
index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
index_name = options[:name].to_s if options.key?(:name)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ if index_algorithms.key?(options[:algorithm])
+ algorithm = index_algorithms[options[:algorithm]]
+ elsif options[:algorithm].present?
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
@@ -670,6 +796,7 @@ module ActiveRecord
index_type = options
max_index_length = allowed_index_name_length
+ algorithm = using = nil
end
if index_name.length > max_index_length
@@ -680,7 +807,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options]
+ [index_name, index_type, index_columns, index_options, algorithm, using]
end
def index_name_for_remove(table_name, options = {})
@@ -730,8 +857,12 @@ module ActiveRecord
end
private
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
+ end
+
+ def create_alter_table(name)
+ AlterTable.new create_table_definition(name, false, {})
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 73c80a3220..2b6685499a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -78,7 +78,7 @@ module ActiveRecord
@joinable = options.fetch(:joinable, true)
end
- # This state is necesarry so that we correctly handle stuff that might
+ # This state is necessary so that we correctly handle stuff that might
# happen in a commit/rollback. But it's kinda distasteful. Maybe we can
# find a better way to structure it in the future.
def finishing?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7949bcb5ce..2ccde15a26 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -18,6 +18,7 @@ module ActiveRecord
autoload :ColumnDefinition
autoload :TableDefinition
autoload :Table
+ autoload :AlterTable
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -100,6 +101,79 @@ module ActiveRecord
@visitor = nil
end
+ def valid_type?(type)
+ true
+ end
+
+ class SchemaCreation
+ def initialize(conn)
+ @conn = conn
+ @cache = {}
+ end
+
+ def accept(o)
+ m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
+ send m, o
+ end
+
+ private
+
+ def visit_AlterTable(o)
+ sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ end
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ column_sql = "#{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ column_sql
+ end
+
+ def visit_TableDefinition(o)
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
+ create_sql << "#{quote_table_name(o.name)} ("
+ create_sql << o.columns.map { |c| accept c }.join(', ')
+ create_sql << ") #{o.options}"
+ create_sql
+ end
+
+ def column_options(o)
+ column_options = {}
+ column_options[:null] = o.null unless o.null.nil?
+ column_options[:default] = o.default unless o.default.nil?
+ column_options[:column] = o
+ column_options
+ end
+
+ def quote_column_name(name)
+ @conn.quote_column_name name
+ end
+
+ def quote_table_name(name)
+ @conn.quote_table_name name
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ @conn.type_to_sql type.to_sym, limit, precision, scale
+ end
+
+ def add_column_options!(column_sql, column_options)
+ @conn.add_column_options! column_sql, column_options
+ column_sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
def lease
synchronize do
unless in_use
@@ -212,6 +286,13 @@ module ActiveRecord
[]
end
+ # A list of index algorithms, to be filled by adapters that
+ # support them. MySQL and PostgreSQL has support for them right
+ # now.
+ def index_algorithms
+ {}
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
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 f88f5742a8..b0b160f9b4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -3,13 +3,34 @@ require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ add_column_position!(super, o)
+ end
+
+ def add_column_position!(sql, column)
+ if column.first
+ sql << " FIRST"
+ elsif column.after
+ sql << " AFTER #{quote_column_name(column.after)}"
+ end
+ sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict
+ attr_reader :collation, :strict, :extra
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict
@collation = collation
-
+ @extra = extra
super(name, default, sql_type, null)
end
@@ -130,6 +151,9 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ INDEX_TYPES = [:fulltext, :spatial]
+ INDEX_USINGS = [:btree, :hash]
+
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
include Arel::Visitors::BindVisitor
end
@@ -187,6 +211,10 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
+ def index_algorithms
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
+ end
+
# HELPER METHODS ===========================================
# The two drivers have slightly different ways of yielding hashes of results, so
@@ -196,8 +224,8 @@ module ActiveRecord
end
# Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, extra)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -410,7 +438,11 @@ module ActiveRecord
if current_index != row[:Key_name]
next if row[:Key_name] == 'PRIMARY' # skip the primary key
current_index = row[:Key_name]
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
end
indexes.last.columns << row[:Column_name]
@@ -426,7 +458,7 @@ module ActiveRecord
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
end
end
end
@@ -459,10 +491,6 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
def change_column_default(table_name, column_name, default)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
@@ -487,6 +515,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
@@ -572,6 +605,10 @@ module ActiveRecord
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
@@ -651,6 +688,7 @@ module ActiveRecord
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
options[:null] = column.null
+ options[:auto_increment] = (column.extra == "auto_increment")
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 99ea1ee0ca..530a27d099 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -38,6 +38,15 @@ module ActiveRecord
configure_connection
end
+ MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
+ def initialize_schema_migrations_table
+ if @config[:encoding] == 'utf8mb4'
+ ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
+ else
+ ActiveRecord::SchemaMigration.create_table
+ end
+ end
+
def supports_explain?
true
end
@@ -54,8 +63,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c836c5d8e8..09ba2e0d4a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -150,8 +150,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 3d8f0b575c..14ef07a75e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -2,6 +2,17 @@ module ActiveRecord
module ConnectionAdapters
class PostgreSQLColumn < Column
module Cast
+ def point_to_string(point)
+ "(#{point[0]},#{point[1]})"
+ end
+
+ def string_to_point(string)
+ if string[0] == '(' && string[-1] == ')'
+ string = string[1...-1]
+ end
+ string.split(',').map{ |v| Float(v) }
+ end
+
def string_to_time(string)
return string unless String === string
@@ -30,8 +41,8 @@ module ActiveRecord
nil
elsif String === string
Hash[string.scan(HstorePair).map { |k,v|
- v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
- k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
[k,v]
}]
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 68f2f2ca7b..51f377dfd7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -63,6 +63,16 @@ module ActiveRecord
end
end
+ class Point < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_point value
+ else
+ value
+ end
+ end
+ end
+
class Array < Type
attr_reader :subtype
def initialize(subtype)
@@ -330,6 +340,7 @@ module ActiveRecord
register_type 'time', OID::Time.new
register_type 'path', OID::Identity.new
+ register_type 'point', OID::Point.new
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 47e2e3928f..6329733abc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -18,27 +18,33 @@ module ActiveRecord
def quote(value, column = nil) #:nodoc:
return super unless column
+ sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
+
case value
when Range
- if /range$/ =~ column.sql_type
- "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}"
+ if /range$/ =~ sql_type
+ "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}"
else
super
end
when Array
- if column.array
- "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ case sql_type
+ when 'point' then super(PostgreSQLColumn.point_to_string(value))
else
- super
+ if column.array
+ "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ else
+ super
+ end
end
when Hash
- case column.sql_type
+ case sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when IPAddr
- case column.sql_type
+ case sql_type
when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
else super
end
@@ -51,11 +57,14 @@ module ActiveRecord
super
end
when Numeric
- return super unless column.sql_type == 'money'
- # Not truly string input, so doesn't require (or allow) escape string syntax.
- "'#{value}'"
+ if sql_type == 'money' || [:string, :text].include?(column.type)
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
+ "'#{value}'"
+ else
+ super
+ end
when String
- case column.sql_type
+ case sql_type
when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
@@ -87,8 +96,12 @@ module ActiveRecord
super(value, column)
end
when Array
- return super(value, column) unless column.array
- PostgreSQLColumn.array_to_string(value, column, self)
+ case column.sql_type
+ when 'point' then PostgreSQLColumn.point_to_string(value)
+ else
+ return super(value, column) unless column.array
+ PostgreSQLColumn.array_to_string(value, column, self)
+ end
when String
return super(value, column) unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
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 3bc61c5e0c..0168c36abc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,6 +1,42 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql = super
+ if o.primary_key? && o.type == :uuid
+ sql << " PRIMARY KEY "
+ add_column_options!(sql, column_options(o))
+ end
+ sql
+ end
+
+ def add_column_options!(sql, options)
+ if options[:array] || options[:column].try(:array)
+ sql << '[]'
+ end
+
+ column = options.fetch(:column) { return super }
+ if column.type == :uuid && options[:default] =~ /\(\)/
+ sql << " DEFAULT #{options[:default]}"
+ else
+ super
+ end
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -124,8 +160,9 @@ module ActiveRecord
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
end.compact
end
@@ -337,10 +374,7 @@ module ActiveRecord
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
clear_cache!
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
-
- execute add_column_sql
+ super
end
# Changes the column of a table.
@@ -375,6 +409,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ end
+
def remove_index!(table_name, index_name) #:nodoc:
execute "DROP INDEX #{quote_table_name(index_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2115d960fa..e34e1fc10c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -330,6 +330,13 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ def primary_key(name, type = :primary_key, options = {})
+ return super unless type == :uuid
+ options[:default] ||= 'uuid_generate_v4()'
+ options[:primary_key] = true
+ column name, type, options
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -338,13 +345,14 @@ module ActiveRecord
self
end
+ def xml(options = {})
+ column(args[0], :text, options)
+ end
+
private
- def new_column_definition(base, name, type)
- definition = ColumnDefinition.new base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
end
@@ -425,6 +433,10 @@ module ActiveRecord
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -518,8 +530,7 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query 'SELECT 1'
- true
+ @connection.connect_poll != PG::PGRES_POLLING_FAILED
rescue PGError
false
end
@@ -594,13 +605,13 @@ module ActiveRecord
end
def enable_extension(name)
- exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap {
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
reload_type_map
}
end
def disable_extension(name)
- exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap {
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
reload_type_map
}
end
@@ -627,13 +638,6 @@ module ActiveRecord
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
- def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
- sql << '[]'
- end
- super
- end
-
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
@@ -663,6 +667,10 @@ module ActiveRecord
@use_insert_returning
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -707,7 +715,14 @@ module ActiveRecord
# populate composite types
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].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
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ end
+
OID::TYPE_MAP[row['oid'].to_i] = vector
end
@@ -894,8 +909,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 5839d1d3b4..1d7a22e831 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,7 +1,9 @@
+require 'active_support/deprecation/reporting'
+
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :primary_keys, :tables, :version
+ attr_reader :version
attr_accessor :connection
def initialize(conn)
@@ -14,6 +16,15 @@ module ActiveRecord
prepare_default_proc
end
+ def primary_keys(table_name = nil)
+ if table_name
+ @primary_keys[table_name]
+ else
+ ActiveSupport::Deprecation.warn('call primary_keys with a table name!')
+ @primary_keys.dup
+ end
+ end
+
# A cached lookup for table existence.
def table_exists?(name)
return @tables[name] if @tables.key? name
@@ -30,12 +41,22 @@ module ActiveRecord
end
end
+ def tables(name = nil)
+ if name
+ @tables[name]
+ else
+ ActiveSupport::Deprecation.warn('call tables with a name!')
+ @tables.dup
+ end
+ end
+
# Get the columns for a table
def columns(table = nil)
if table
@columns[table]
else
- @columns
+ ActiveSupport::Deprecation.warn('call columns with a table name!')
+ @columns.dup
end
end
@@ -45,7 +66,8 @@ module ActiveRecord
if table
@columns_hash[table]
else
- @columns_hash
+ ActiveSupport::Deprecation.warn('call columns_hash with a table name!')
+ @columns_hash.dup
end
end
@@ -58,6 +80,12 @@ module ActiveRecord
@version = nil
end
+ def size
+ [@columns, @columns_hash, @primary_keys, @tables].map { |x|
+ x.size
+ }.inject :+
+ end
+
# Clear out internal caches for table with +table_name+.
def clear_table_cache!(table_name)
@columns.delete table_name
@@ -69,9 +97,9 @@ module ActiveRecord
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version
- [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
- self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
- end
+ [@version] + [@columns, @columns_hash, @primary_keys, @tables].map { |val|
+ Hash[val]
+ }
end
def marshal_load(array)
@@ -87,7 +115,7 @@ module ActiveRecord
end
@columns_hash.default_proc = Proc.new do |h, table_name|
- h[table_name] = Hash[columns[table_name].map { |col|
+ h[table_name] = Hash[columns(table_name).map { |col|
[col.name, col]
}]
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 49966678d6..7d940fe1c9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -458,7 +458,7 @@ module ActiveRecord
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
+ definition.remove_column column_name
end
end
@@ -583,9 +583,17 @@ module ActiveRecord
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
quoted_to = quote_table_name(to)
+
+ raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
+
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
- sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
+
+ column_values = columns.map do |col|
+ quote(row[column_mappings[col]], raw_column_mappings[col])
+ end
+
+ sql << column_values * ', '
sql << ')'
exec_query sql
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 899fe7d7c7..db5e7b82ca 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -69,8 +69,25 @@ module ActiveRecord
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
- class_attribute :connection_handler, instance_writer: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ ##
+ # :singleton-method:
+ # Disable implicit join references. This feature was deprecated with Rails 4.
+ # If you don't make use of implicit references but still see deprecation warnings
+ # you can disable the feature entirely. This will be the default with Rails 4.1.
+ mattr_accessor :disable_implicit_join_references, instance_writer: false
+ self.disable_implicit_join_references = false
+
+ class_attribute :default_connection_handler, instance_writer: false
+
+ def self.connection_handler
+ Thread.current[:active_record_connection_handler] || self.default_connection_handler
+ end
+
+ def self.connection_handler=(handler)
+ Thread.current[:active_record_connection_handler] = handler
+ end
+
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
module ClassMethods
@@ -168,6 +185,7 @@ module ActiveRecord
@columns_hash = self.class.column_types.dup
init_internals
+ init_changed_attributes
ensure_proper_type
populate_with_current_scope_attributes
@@ -238,9 +256,7 @@ module ActiveRecord
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@changed_attributes = {}
- self.class.column_defaults.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
- end
+ init_changed_attributes
@aggregation_cache = {}
@association_cache = {}
@@ -324,6 +340,7 @@ module ActiveRecord
# also be used to "borrow" the connection to do database work that isn't
# easily done without going straight to SQL.
def connection
+ ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class")
self.class.connection
end
@@ -418,11 +435,21 @@ module ActiveRecord
@readonly = false
@destroyed = false
@marked_for_destruction = false
+ @destroyed_by_association = nil
@new_record = true
@txn = nil
@_start_transaction_state = {}
@transaction_state = nil
@reflects_state = [false]
end
+
+ def init_changed_attributes
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
+ # optimistic locking) won't get written unless they get marked as changed
+ self.class.columns.each do |c|
+ attr, orig_value = c.name, c.default
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 81f92db271..81cca37939 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# ==== Parameters
#
# * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
+ # * +counters+ - One or more association counters to reset
#
# ==== Examples
#
@@ -21,6 +21,7 @@ module ActiveRecord
object = find(id)
counters.each do |association|
has_many_association = reflect_on_association(association.to_sym)
+ raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
has_many_association = has_many_association.through_reflection
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 3135465dfe..15736575a2 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -9,7 +9,7 @@ module ActiveRecord
yield
return current[:available_queries_for_explain]
ensure
- # Note that the return value above does not depend on this assigment.
+ # Note that the return value above does not depend on this assignment.
current[:available_queries_for_explain] = original
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 11b53275e1..fbd7a4d891 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -24,7 +24,6 @@ module ActiveRecord
rows.each(&block)
end
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
private
def rows
@@ -32,7 +31,7 @@ module ActiveRecord
begin
data = YAML.load(render(IO.read(@file)))
- rescue *RESCUE_ERRORS => error
+ rescue ArgumentError, Psych::SyntaxError => error
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
end
@rows = data ? validate(data).to_a : []
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2958d08210..c26fc76515 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -752,7 +752,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
- fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] }
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 701949e57b..209de78898 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -86,7 +86,7 @@ module ActiveRecord
)
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
- affected_rows = connection.update stmt
+ affected_rows = self.class.connection.update stmt
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
@@ -117,7 +117,7 @@ module ActiveRecord
if locking_enabled?
column_name = self.class.locking_column
column = self.class.columns_hash[column_name]
- substitute = connection.substitute_at(column, relation.bind_values.length)
+ substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
relation.bind_values << [column, self[column_name].to_i]
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5d7762ec3a..d3edcf3cdb 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -102,7 +102,7 @@ module ActiveRecord
# table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
- # the table called +name+. It makes the table object availabe to a block that
+ # the table called +name+. It makes the table object available to a block that
# can then add/remove columns, indexes or foreign keys to it.
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
# to +new_name+.
@@ -858,7 +858,7 @@ module ActiveRecord
end
def current_version
- migrated.sort.last || 0
+ migrated.max || 0
end
def current_migration
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 85fb4be992..ac2d2f2712 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -205,7 +205,7 @@ module ActiveRecord
# Returns an array of column objects for the table associated with this class.
def columns
- @columns ||= connection.schema_cache.columns[table_name].map do |col|
+ @columns ||= connection.schema_cache.columns(table_name).map do |col|
col = col.dup
col.primary = (col.name == primary_key)
col
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 347f023793..42cece3ad0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -410,7 +410,7 @@ module ActiveRecord
def relation_for_destroy
pk = self.class.primary_key
column = self.class.columns_hash[pk]
- substitute = connection.substitute_at(column, 0)
+ substitute = self.class.connection.substitute_at(column, 0)
relation = self.class.unscoped.where(
self.class.arel_table[pk].eq(substitute))
@@ -443,7 +443,7 @@ module ActiveRecord
real_column = db_columns_with_values[i].first
bind_attrs[column] = klass.connection.substitute_at(real_column, i)
end
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs)
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs)
klass.connection.update stmt, 'SQL', db_columns_with_values
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index e04a3d0976..902fd90c54 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :references, :none, :to => :all
+ :having, :create_with, :uniq, :distinct, :references, :none, :to => :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 13f3bf7085..99117b74c5 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -49,7 +49,7 @@ module ActiveRecord
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
- runner do |app|
+ runner do
require "active_record/base"
end
@@ -64,7 +64,7 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
- initializer "active_record.migration_error" do |app|
+ initializer "active_record.migration_error" do
if config.active_record.delete(:migration_error) == :page_load
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::Migration::CheckPending"
@@ -158,7 +158,7 @@ module ActiveRecord
end
# Expose database runtime to controller for logging.
- initializer "active_record.log_runtime" do |app|
+ initializer "active_record.log_runtime" do
require "active_record/railties/controller_runtime"
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ad54ba55b6..4d24654015 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
:extending]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :uniq, :create_with]
+ :reverse_order, :distinct, :create_with]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
@@ -506,6 +506,12 @@ module ActiveRecord
includes_values & joins_values
end
+ # +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+
+ # to maintain backwards compatibility. Use +distinct_value+ instead.
+ def uniq_value
+ distinct_value
+ end
+
# Compares two relations for equality.
def ==(other)
case other
@@ -589,21 +595,25 @@ module ActiveRecord
if (references_values - joined_tables).any?
true
- elsif (string_tables - joined_tables).any?
+ elsif !ActiveRecord::Base.disable_implicit_join_references &&
+ (string_tables - joined_tables).any?
ActiveSupport::Deprecation.warn(
"It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
"that are referenced in a string SQL snippet. For example: \n" \
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
"\n" \
- "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
"comments table to the query, rather than loading comments in a separate query. " \
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
"Since we don't want to write an SQL parser, we are removing this functionality. " \
"From now on, you must explicitly tell Active Record when you are referencing a table " \
"from a string:\n" \
"\n" \
- " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n"
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
+ "\n" \
+ "If you don't rely on implicit join references you can disable the feature entirely " \
+ "by setting `config.active_record.disable_implicit_join_references = true`."
)
true
else
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index d24f8233a8..4f60704790 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# Person.count(:all)
# # => performs a COUNT(*) (:all is an alias for '*')
#
- # Person.count(:age, distinct: true)
+ # Person.distinct.count(:age)
# # => counts the number of different age values
#
# If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
@@ -198,8 +198,13 @@ module ActiveRecord
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
- # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
- distinct = options[:distinct] || self.uniq_value
+ # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
+ distinct = self.distinct_value
+ if options.has_key?(:distinct)
+ ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \
+ "Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)"
+ distinct = options[:distinct]
+ end
if operation == "count"
column_name ||= (select_for_count || :all)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 00a506c3a7..1b6239eb38 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -37,11 +37,9 @@ module ActiveRecord
end
RUBY
else
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method}(*args, &block)
- scoping { @klass.send(#{method.inspect}, *args, &block) }
- end
- RUBY
+ define_method method do |*args, &block|
+ scoping { @klass.send(method, *args, &block) }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index bd783a94cf..f44d46d15b 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -48,7 +48,7 @@ module ActiveRecord
column = reflection.foreign_key
end
- queries << build(table[column.to_sym], value)
+ queries << build(table[column], value)
queries
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 588e3f2ddd..257221174b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -710,20 +710,22 @@ module ActiveRecord
# User.select(:name)
# # => Might return two records with the same name
#
- # User.select(:name).uniq
- # # => Returns 1 record per unique name
+ # User.select(:name).distinct
+ # # => Returns 1 record per distinct name
#
- # User.select(:name).uniq.uniq(false)
+ # User.select(:name).distinct.distinct(false)
# # => You can also remove the uniqueness
- def uniq(value = true)
- spawn.uniq!(value)
+ def distinct(value = true)
+ spawn.distinct!(value)
end
+ alias uniq distinct
- # Like #uniq, but modifies relation in place.
- def uniq!(value = true) # :nodoc:
- self.uniq_value = value
+ # Like #distinct, but modifies relation in place.
+ def distinct!(value = true) # :nodoc:
+ self.distinct_value = value
self
end
+ alias uniq! distinct!
# Used to extend a scope with additional methods, either through
# a module or through a block provided.
@@ -814,7 +816,7 @@ module ActiveRecord
build_select(arel, select_values.uniq)
- arel.distinct(uniq_value)
+ arel.distinct(distinct_value)
arel.from(build_from) if from_value
arel.lock(lock_value) if lock_value
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index df090b972d..10c6d272cd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -118,7 +118,7 @@ HEADER
# then dump all non-primary key columns
column_specs = columns.map do |column|
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
@connection.column_spec(column, @types)
end.compact
@@ -185,6 +185,10 @@ HEADER
statement_parts << ('where: ' + index.where.inspect) if index.where
+ statement_parts << ('using: ' + index.using.inspect) if index.using
+
+ statement_parts << ('type: ' + index.type.inspect) if index.type
+
' ' + statement_parts.join(', ')
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 9830abe7d8..6077144265 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -13,18 +13,21 @@ module ActiveRecord
"#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
end
- def self.create_table
+ def self.create_table(limit=nil)
unless connection.table_exists?(table_name)
- connection.create_table(table_name, :id => false) do |t|
- t.column :version, :string, :null => false
+ version_options = {null: false}
+ version_options[:limit] = limit if limit
+
+ connection.create_table(table_name, id: false) do |t|
+ t.column :version, :string, version_options
end
- connection.add_index table_name, :version, :unique => true, :name => index_name
+ connection.add_index table_name, :version, unique: true, name: index_name
end
end
def self.drop_table
if connection.table_exists?(table_name)
- connection.remove_index table_name, :name => index_name
+ connection.remove_index table_name, name: index_name
connection.drop_table(table_name)
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5bd481082e..cde4f29d08 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -27,14 +27,6 @@ module ActiveRecord
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
- #
- # It is recommended that you use the block form of unscoped because
- # chaining unscoped with +scope+ does not work. Assuming that
- # +published+ is a +scope+, the following two statements
- # are equal: the +default_scope+ is applied on both.
- #
- # Post.unscoped.published
- # Post.published
def unscoped
block_given? ? relation.scoping { yield } : relation
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 10696258c9..50569d2462 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -26,7 +26,9 @@ module ActiveRecord
$stdout.print error.error
establish_connection root_configuration_without_database
connection.create_database configuration['database'], creation_options
- connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ if configuration['username'] != 'root'
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ end
establish_connection configuration
else
$stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 33718ef0e9..4dbd668fcf 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -160,7 +160,7 @@ module ActiveRecord
# end
# end
#
- # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)
+ # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
#
# Most databases don't support true nested transactions. At the time of
# writing, the only database that we're aware of that supports true nested
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3706885881..26dca415ff 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -74,8 +74,7 @@ module ActiveRecord
protected
def perform_validations(options={}) # :nodoc:
- perform_validation = options[:validate] != false
- perform_validation ? valid?(options[:context]) : true
+ options[:validate] == false || valid?(options[:context])
end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 1427189851..a705d8c2c4 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -2,6 +2,10 @@ module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
def initialize(options)
+ if options[:conditions] && !options[:conditions].respond_to?(:call)
+ raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
+ "Pass a callable instead: `conditions: -> { where(approved: true) }`"
+ end
super({ case_sensitive: true }.merge!(options))
end
@@ -19,7 +23,7 @@ module ActiveRecord
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
relation = scope_relation(record, table, relation)
relation = finder_class.unscoped.where(relation)
- relation.merge!(options[:conditions]) if options[:conditions]
+ relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
error_options = options.except(:case_sensitive, :scope, :conditions)
@@ -116,7 +120,7 @@ module ActiveRecord
# of the title attribute:
#
# class Article < ActiveRecord::Base
- # validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
+ # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
# end
#
# When the record is created, a check is performed to make sure that no
@@ -132,7 +136,7 @@ module ActiveRecord
# the uniqueness constraint.
# * <tt>:conditions</tt> - Specify the conditions to be included as a
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
- # (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
+ # (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
# non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index c0471bb506..f2b041ad97 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,10 +1,11 @@
module ActiveRecord
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
+ STRING = ActiveRecord.version.to_s
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index 3a3cf86d73..fd94a2d038 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -2,8 +2,12 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def change
create_table :<%= table_name %> do |t|
<% attributes.each do |attribute| -%>
+<% if attribute.password_digest? -%>
+ t.string :password_digest<%= attribute.inject_options %>
+<% else -%>
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
<% end -%>
+<% end -%>
<% if options[:timestamps] %>
t.timestamps
<% end -%>
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
index 056f55470c..808598699b 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
@@ -1,7 +1,10 @@
<% module_namespacing do -%>
class <%= class_name %> < <%= parent_class_name.classify %>
-<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
+<% attributes.select(&:reference?).each do |attribute| -%>
belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
<% end -%>
+<% if attributes.any?(&:password_digest?) -%>
+ has_secure_password
+<% end -%>
end
<% end -%>
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index bd114995f7..e28bb7b6ca 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -167,7 +167,7 @@ module ActiveRecord
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
- # should deleted created record as otherwise disable_referential_integrity will try to enable constraints after executed block
+ # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block
# and will fail (at least on Oracle)
@connection.execute "DELETE FROM fk_test_has_fk"
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 8812cf1b7d..e6d0183b11 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -21,20 +21,44 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :type => type)
+ end
+
+ %w(btree hash).each do |using|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => using)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
end
@@ -70,8 +94,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def test_add_timestamps
with_real_execute do
begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
+ ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index b965983fec..1844a2e0dc 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -17,7 +17,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_connect_with_url
- run_without_connection do |orig|
+ run_without_connection do
ar_config = ARTest.connection_config['arunit']
skip "This test doesn't work with custom socket location" if ar_config['socket']
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 0eb1cc511e..a75883cd3a 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -16,6 +16,15 @@ module ActiveRecord
eosql
end
+ def test_valid_column
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @conn.valid_type?(:foobar)
+ end
+
def test_client_encoding
assert_equal Encoding::UTF_8, @conn.client_encoding
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index d94bb629a7..807a7a155e 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -35,6 +35,28 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
+
+ def test_dump_indexes
+ index_a_name = 'index_key_tests_on_snack'
+ index_b_name = 'index_key_tests_on_pizza'
+ index_c_name = 'index_key_tests_on_awesome'
+
+ table = 'key_tests'
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 3,indexes.size
+
+ index_a = indexes.select{|i| i.name == index_a_name}[0]
+ index_b = indexes.select{|i| i.name == index_b_name}[0]
+ index_c = indexes.select{|i| i.name == index_c_name}[0]
+ assert_equal :btree, index_a.using
+ assert_nil index_a.type
+ assert_equal :btree, index_b.using
+ assert_nil index_b.type
+
+ assert_nil index_c.using
+ assert_equal :fulltext, index_c.type
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index a83399d0cd..8a2a7ef269 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -21,20 +21,44 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :type => type)
+ end
+
+ %w(btree hash).each do |using|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => using)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
end
@@ -70,8 +94,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def test_add_timestamps
with_real_execute do
begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
+ ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 98596a7f62..e76617b845 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -84,7 +84,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { x.save }
assert_nothing_raised { Group.find_by_order('y') }
assert_nothing_raised { Group.find(1) }
- x = Group.find(1)
end
# has_one association with reserved-word table name
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
new file mode 100644
index 0000000000..9ecd901eac
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2Adapter
+ class SchemaMigrationsTest < ActiveRecord::TestCase
+ def test_initializes_schema_migrations_for_encoding_utf8mb4
+ conn = ActiveRecord::Base.connection
+
+ smtn = ActiveRecord::Migrator.schema_migrations_table_name
+ conn.drop_table(smtn) if conn.table_exists?(smtn)
+
+ config = conn.instance_variable_get(:@config)
+ original_encoding = config[:encoding]
+
+ config[:encoding] = 'utf8mb4'
+ conn.initialize_schema_migrations_table
+
+ assert conn.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4)
+ ensure
+ config[:encoding] = original_encoding
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 94429e772f..5db60ff8a0 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -44,6 +44,27 @@ module ActiveRecord
assert_match(/database 'foo-bar'/, e.inspect)
end
+ def test_dump_indexes
+ index_a_name = 'index_key_tests_on_snack'
+ index_b_name = 'index_key_tests_on_pizza'
+ index_c_name = 'index_key_tests_on_awesome'
+
+ table = 'key_tests'
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 3,indexes.size
+
+ index_a = indexes.select{|i| i.name == index_a_name}[0]
+ index_b = indexes.select{|i| i.name == index_b_name}[0]
+ index_c = indexes.select{|i| i.name == index_c_name}[0]
+ assert_equal :btree, index_a.using
+ assert_nil index_a.type
+ assert_equal :btree, index_b.using
+ assert_nil index_b.type
+
+ assert_nil index_c.using
+ assert_equal :fulltext, index_c.type
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 01c3e6b49b..16329689c0 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -29,9 +29,29 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
false
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
+
+ %w(gin gist hash btree).each do |type|
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type)
+
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
+ end
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :copy)
+ end
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index c03660957e..6b726ce875 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -81,42 +81,6 @@ module ActiveRecord
assert_equal 'SCHEMA', @connection.logged[0][1]
end
- def test_reconnection_after_simulated_disconnection_with_verify
- assert @connection.active?
- original_connection_pid = @connection.query('select pg_backend_pid()')
-
- # Fail with bad connection on next query attempt.
- raw_connection = @connection.raw_connection
- raw_connection_class = class << raw_connection ; self ; end
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def query_fake(*args)
- if !( @called ||= false )
- self.stubs(:status).returns(PGconn::CONNECTION_BAD)
- @called = true
- raise PGError
- else
- self.unstub(:status)
- query_unfake(*args)
- end
- end
-
- alias query_unfake query
- alias query query_fake
- CODE
-
- begin
- @connection.verify!
- new_connection_pid = @connection.query('select pg_backend_pid()')
- ensure
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- alias query query_unfake
- undef query_fake
- CODE
- end
-
- assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid"
- end
-
# Must have with_manual_interventions set to true for this
# test to run.
# When prompted, restart the PostgreSQL server with the
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index ad98d7c8ce..e434b4861c 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -40,25 +40,15 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert @connection.extensions.include?('hstore'), "extension list should include hstore"
end
- def test_hstore_enabled
+ def test_disable_enable_hstore
assert @connection.extension_enabled?('hstore')
- end
-
- def test_disable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- assert_not @connection.extension_enabled?('hstore')
- end
- end
-
- def test_enable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- end
-
+ @connection.disable_extension 'hstore'
assert_not @connection.extension_enabled?('hstore')
@connection.enable_extension 'hstore'
assert @connection.extension_enabled?('hstore')
+ ensure
+ # Restore column(s) dropped by `drop extension hstore cascade;`
+ load_schema
end
def test_column
@@ -189,6 +179,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_cycle('ca' => 'cà', 'ac' => 'àc')
end
+ def test_multiline
+ assert_cycle("a\nb" => "c\nd")
+ end
+
private
def assert_cycle hash
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 05e0f0e192..17d77c5454 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -10,6 +10,15 @@ module ActiveRecord
@connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
+ def test_valid_column
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ assert @connection.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @connection.valid_type?(:foobar)
+ end
+
def test_primary_key
assert_equal 'id', @connection.primary_key('ex')
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 685f0ea74f..b3429648ee 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -44,6 +44,14 @@ module ActiveRecord
c = Column.new(nil, 1, 'float')
assert_equal "'Infinity'", @conn.quote(infinity, c)
end
+
+ def test_quote_cast_numeric
+ fixnum = 666
+ c = Column.new(nil, nil, 'string')
+ assert_equal "'666'", @conn.quote(fixnum, c)
+ c = Column.new(nil, nil, 'text')
+ assert_equal "'666'", @conn.quote(fixnum, c)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index cd31900d4e..e8dd188ec8 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -11,16 +11,19 @@ class SchemaTest < ActiveRecord::TestCase
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
INDEX_C_NAME = 'c_index_full_text_search'
INDEX_D_NAME = 'd_index_things_on_description_desc'
+ INDEX_E_NAME = 'e_index_things_on_name_vector'
INDEX_A_COLUMN = 'name'
INDEX_B_COLUMN_S1 = 'email'
INDEX_B_COLUMN_S2 = 'moment'
INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
INDEX_D_COLUMN = 'description'
+ INDEX_E_COLUMN = 'name_vector'
COLUMNS = [
'id integer',
'name character varying(50)',
'email character varying(50)',
'description character varying(100)',
+ 'name_vector tsvector',
'moment timestamp without time zone default now()'
]
PK_TABLE_NAME = 'table_with_pk'
@@ -61,6 +64,8 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
@@ -236,15 +241,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_dump_indexes_for_schema_one
- do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_two
- do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_multiple_schemas_in_search_path
- do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_with_uppercase_index_name
@@ -344,15 +349,20 @@ class SchemaTest < ActiveRecord::TestCase
@connection.schema_search_path = "'$user', public"
end
- def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
+ def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
- assert_equal 3,indexes.size
+ assert_equal 4,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
+ do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
+ indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
+ assert_equal :btree, index.using
+ end
+ assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
new file mode 100644
index 0000000000..c0c0e8898c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlUUIDTest < ActiveRecord::TestCase
+ class UUID < ActiveRecord::Base
+ self.table_name = 'pg_uuids'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ unless @connection.supports_extensions?
+ return skip "do not test on PG without uuid-ossp"
+ end
+
+ unless @connection.extension_enabled?('uuid-ossp')
+ @connection.enable_extension 'uuid-ossp'
+ @connection.commit_db_transaction
+ end
+
+ @connection.reconnect!
+
+ @connection.transaction do
+ @connection.create_table('pg_uuids', id: :uuid) do |t|
+ t.string 'name'
+ t.uuid 'other_uuid', default: 'uuid_generate_v4()'
+ end
+ end
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists pg_uuids'
+ end
+
+ def test_id_is_uuid
+ assert_equal :uuid, UUID.columns_hash['id'].type
+ assert UUID.primary_key
+ end
+
+ def test_id_has_a_default
+ u = UUID.create
+ assert_not_nil u.id
+ end
+
+ def test_auto_create_uuid
+ u = UUID.create
+ u.reload
+ assert_not_nil u.other_uuid
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index d03d1dd94c..a5a22bc30b 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class CopyTableTest < ActiveRecord::TestCase
- fixtures :customers, :companies, :comments
+ fixtures :customers, :companies, :comments, :binaries
def setup
@connection = ActiveRecord::Base.connection
@@ -72,6 +72,10 @@ class CopyTableTest < ActiveRecord::TestCase
end
end
+ def test_copy_table_with_binary_column
+ test_copy_table 'binaries', 'binaries2'
+ end
+
protected
def copy_table(from, to, options = {})
@connection.copy_table(from, to, {:temporary => true}.merge(options))
@@ -86,7 +90,7 @@ protected
end
def table_indexes_without_name(table)
- @connection.indexes('comments_with_index').delete(:name)
+ @connection.indexes(table).delete(:name)
end
def row_count(table)
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 003052bac4..d51d425c3c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -25,6 +25,19 @@ module ActiveRecord
@conn.intercepted = true
end
+ def test_valid_column
+ column = @conn.columns('items').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ # sqlite databses should be able to support any type and not
+ # just the ones mentioned in the native_database_types.
+ # Therefore test_invalid column should always return true
+ # even if the type is not valid.
+ def test_invalid_column
+ assert @conn.valid_type?(:foobar)
+ end
+
def teardown
@conn.intercepted = false
@conn.logged = []
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 80bca7f63e..e693d34f99 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -55,7 +55,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_nothing_raised do
assert_equal 4, categories.count
assert_equal 4, categories.to_a.count
- assert_equal 3, categories.count(:distinct => true)
+ assert_equal 3, categories.distinct.count
assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 1de7ee0846..d6850215b5 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -193,7 +193,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def test_finding_with_includes_on_has_one_assocation_with_same_include_includes_only_once
+ def test_finding_with_includes_on_has_one_association_with_same_include_includes_only_once
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
@@ -467,7 +467,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts_with_comments = people(:michael).posts.merge(:includes => :comments, :order => 'posts.id').to_a
posts_with_author = people(:michael).posts.merge(:includes => :author, :order => 'posts.id').to_a
posts_with_comments_and_author = people(:michael).posts.merge(:includes => [ :comments, :author ], :order => 'posts.id').to_a
- assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
+ assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum + post.comments.size }
assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
end
@@ -523,7 +523,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit
posts = Post.all.merge!(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).to_a
assert_equal 2, posts.size
- assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
+ assert_equal 3, posts.inject(0) { |sum, post| sum + post.comments.size }
end
def test_eager_with_has_many_and_limit_and_conditions
@@ -1174,6 +1174,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" }
end
+ test "preloading a through association twice does not reset it" do
+ members = Member.includes(current_membership: :club).includes(:club).to_a
+ assert_no_queries {
+ assert_equal 3, members.map(&:current_membership).map(&:club).size
+ }
+ end
+
test "works in combination with order(:symbol)" do
author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first
assert_equal authors(:bob), author
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 1b1b479f1a..84bdca3a97 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
@@ -316,7 +316,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
dev.projects << projects(:active_record)
assert_equal 3, dev.projects.size
- assert_equal 1, dev.projects.uniq.size
+ assert_equal 1, dev.projects.distinct.size
end
def test_uniq_before_the_fact
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 1ddd380f23..781b87741d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -789,6 +789,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_calling_update_attributes_on_id_changes_the_counter_cache
+ topic = Topic.order("id ASC").first
+ original_count = topic.replies.to_a.size
+ assert_equal original_count, topic.replies_count
+
+ first_reply = topic.replies.first
+ first_reply.update_attributes(:parent_id => nil)
+ assert_equal original_count - 1, topic.reload.replies_count
+
+ first_reply.update_attributes(:parent_id => topic.id)
+ assert_equal original_count, topic.reload.replies_count
+ end
+
+ def test_calling_update_attributes_changing_ids_doesnt_change_counter_cache
+ topic1 = Topic.find(1)
+ topic2 = Topic.find(3)
+ original_count1 = topic1.replies.to_a.size
+ original_count2 = topic2.replies.to_a.size
+
+ reply1 = topic1.replies.first
+ reply2 = topic2.replies.first
+
+ reply1.update_attributes(:parent_id => topic2.id)
+ assert_equal original_count1 - 1, topic1.reload.replies_count
+ assert_equal original_count2 + 1, topic2.reload.replies_count
+
+ reply2.update_attributes(:parent_id => topic1.id)
+ assert_equal original_count1, topic1.reload.replies_count
+ assert_equal original_count2, topic2.reload.replies_count
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 4f246f575e..918783e8f1 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -82,7 +82,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
- authors_with_welcoming_post_titles = Author.all.merge!(:joins => :posts, :where => "posts.title like 'Welcome%'").calculate(:count, 'authors.id', :distinct => true)
+ authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, 'authors.id')
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 8c9b4fb921..c8cad84013 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -278,6 +278,31 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert interests[1].man.equal? man
end
+ def test_parent_instance_should_find_child_instance_using_child_instance_id
+ man = Man.create!
+ interest = Interest.create!
+ man.interests = [interest]
+
+ assert interest.equal?(man.interests.first), "The inverse association should use the interest already created and held in memory"
+ assert interest.equal?(man.interests.find(interest.id)), "The inverse association should use the interest already created and held in memory"
+ assert man.equal?(man.interests.first.man), "Two inversion should lead back to the same object that was originally held"
+ assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
+ end
+
+ def test_parent_instance_should_find_child_instance_using_child_instance_id_when_created
+ man = Man.create!
+ interest = Interest.create!(man: man)
+
+ assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held"
+ assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
+
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
+ man.name = "Ben Bitdiddle"
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed"
+ man.interests.find(interest.id).man.name = "Alyssa P. Hacker"
+ assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed"
+ end
+
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 10ec33be75..aabeea025f 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -397,14 +397,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_many
- assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id }
+ assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id }
end
def test_include_has_many_through_polymorphic_has_many
author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
+ assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
end
end
@@ -464,7 +464,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.reload.tags(true).include?(new_tag)
- new_post = Post.new(:title => "Association replacmenet works!", :body => "You best believe it.")
+ new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.")
saved_tag = tags(:general)
new_post.tags << saved_tag
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index e355ed3495..e75d43bda8 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -410,7 +410,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# Mary and Bob both have posts in misc, but they are the only ones.
authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id)
- assert_equal [authors(:mary), authors(:bob)], authors.uniq.sort_by(&:id)
+ assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id)
# Check the polymorphism of taggings is being observed correctly (in both joins)
authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel')
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 201fa5d5a9..a06bacafca 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -95,7 +95,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload
firm = Firm.new("name" => "A New Firm, Inc")
firm.save
- firm.clients.each {|c|} # forcing to load all clients
+ firm.clients.each {} # forcing to load all clients
assert firm.clients.empty?, "New firm shouldn't have client objects"
assert_equal 0, firm.clients.size, "New firm should have 0 clients"
@@ -237,6 +237,11 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert david.projects.scope.is_a?(ActiveRecord::Relation)
assert_equal david.projects, david.projects.scope
end
+
+ test "proxy object is cached" do
+ david = developers(:david)
+ assert david.projects.equal?(david.projects)
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index c503c21e27..648596b828 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -159,8 +159,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_read_attributes_before_type_cast
- category = Category.new({:name=>"Test categoty", :type => nil})
- category_attrs = {"name"=>"Test categoty", "id" => nil, "type" => nil, "categorizations_count" => nil}
+ category = Category.new({:name=>"Test category", :type => nil})
+ category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil}
assert_equal category_attrs , category.attributes_before_type_cast
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index e5cb4f8f7a..536ff4882c 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -439,7 +439,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_assign_ids_for_through_a_belongs_to
- post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
+ post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!")
post.person_ids = [people(:david).id, people(:michael).id]
post.save
post.reload
@@ -1335,7 +1335,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
assert !@pirate.valid?
end
- test "should not automatically asd validate associations without :validate => true" do
+ test "should not automatically add validate associations without :validate => true" do
assert @pirate.valid?
@pirate.non_validated_ship.name = ''
assert @pirate.valid?
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index e5880a6b7b..08223902c7 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -141,13 +141,13 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_limit_should_sanitize_sql_injection_for_limit_without_comas
+ def test_limit_should_sanitize_sql_injection_for_limit_without_commas
assert_raises(ArgumentError) do
Topic.limit("1 select * from schema").to_a
end
end
- def test_limit_should_sanitize_sql_injection_for_limit_with_comas
+ def test_limit_should_sanitize_sql_injection_for_limit_with_commas
assert_raises(ArgumentError) do
Topic.limit("1, 7 procedure help()").to_a
end
@@ -307,20 +307,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("last_read", ex.errors[0].attribute)
end
- def test_initialize_abstract_class
- e = assert_raises(NotImplementedError) do
- FirstAbstractClass.new
- end
- assert_equal("FirstAbstractClass is an abstract class and can not be instantiated.", e.message)
- end
-
- def test_initialize_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)
- end
-
def test_create_after_initialize_without_block
cb = CustomBulb.create(:name => 'Dude')
assert_equal('Dude', cb.name)
@@ -854,7 +840,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
@@ -883,7 +869,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
@@ -894,6 +880,29 @@ class BasicsTest < ActiveRecord::TestCase
objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
assert_equal true, objs[0].isclosed
+
+ # test native ruby formats when defining the geometric types
+ g = Geometric.new(
+ :a_point => [5.0, 6.1],
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ assert g.save
+
+ # Reload and check that we have all the geometric attributes.
+ h = Geometric.find(g.id)
+
+ assert_equal [5.0, 6.1], h.a_point
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
end
end
@@ -1038,7 +1047,7 @@ class BasicsTest < ActiveRecord::TestCase
Joke.reset_sequence_name
end
- def test_dont_clear_inheritnce_column_when_setting_explicitly
+ def test_dont_clear_inheritance_column_when_setting_explicitly
Joke.inheritance_column = "my_type"
before_inherit = Joke.inheritance_column
@@ -1105,7 +1114,7 @@ class BasicsTest < ActiveRecord::TestCase
res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res7 = nil
assert_nothing_raised do
- res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count(distinct: true)
+ res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").distinct.count
end
assert_equal res6, res7
end
@@ -1156,8 +1165,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_find_keeps_multiple_group_values
- combined = Developer.all.merge!(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at').to_a
- assert_equal combined, Developer.all.merge!(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']).to_a
+ combined = Developer.all.merge!(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at, developers.created_on, developers.updated_on').to_a
+ assert_equal combined, Developer.all.merge!(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at', 'developers.created_on', 'developers.updated_on']).to_a
end
def test_find_symbol_ordered_last
@@ -1358,9 +1367,9 @@ class BasicsTest < ActiveRecord::TestCase
def test_clear_cache!
# preheat cache
- c1 = Post.connection.schema_cache.columns['posts']
+ c1 = Post.connection.schema_cache.columns('posts')
ActiveRecord::Base.clear_cache!
- c2 = Post.connection.schema_cache.columns['posts']
+ c2 = Post.connection.schema_cache.columns('posts')
assert_not_equal c1, c2
end
@@ -1499,6 +1508,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal scope, Bird.uniq
end
+ def test_distinct_delegates_to_scoped
+ scope = stub
+ Bird.stubs(:all).returns(mock(:distinct => scope))
+ assert_equal scope, Bird.distinct
+ end
+
def test_table_name_with_2_abstract_subclasses
assert_equal "photos", Photo.table_name
end
@@ -1563,4 +1578,61 @@ class BasicsTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base)
assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values
end
+
+ test "connection_handler can be overridden" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ thread_connection_handler = nil
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ thread_connection_handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal thread_connection_handler, new_handler
+ end
+
+ test "new threads get default the default connection handler" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ handler = nil
+
+ t = Thread.new do
+ handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal handler, orig_handler
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal klass.default_connection_handler, orig_handler
+ end
+
+ test "changing a connection handler in a main thread does not poison the other threads" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ after_handler = nil
+ is_set = false
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ is_set = true
+ Thread.stop
+ after_handler = klass.connection_handler
+ end
+
+ while(!is_set)
+ Thread.pass
+ end
+
+ klass.connection_handler = orig_handler
+ t.wakeup
+ t.join
+
+ assert_equal after_handler, new_handler
+ assert_equal orig_handler, klass.connection_handler
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index acb8b5f562..ba6b0b1362 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -11,7 +11,7 @@ class EachTest < ActiveRecord::TestCase
Post.count('id') # preheat arel's table cache
end
- def test_each_should_excecute_one_query_per_batch
+ def test_each_should_execute_one_query_per_batch
assert_queries(Post.count + 1) do
Post.find_each(:batch_size => 1) do |post|
assert_kind_of Post, post
@@ -19,7 +19,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_each_should_not_return_query_chain_and_execcute_only_one_query
+ def test_each_should_not_return_query_chain_and_execute_only_one_query
assert_queries(1) do
result = Post.find_each(:batch_size => 100000){ }
assert_nil result
@@ -68,7 +68,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_find_in_batches_shouldnt_excute_query_unless_needed
+ def test_find_in_batches_shouldnt_execute_query_unless_needed
post_count = Post.count
assert_queries(2) do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index be49e948fc..c645523905 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -305,8 +305,8 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_selected_field_with_include
- assert_equal 6, Account.includes(:firm).count(:distinct => true)
- assert_equal 4, Account.includes(:firm).select(:credit_limit).count(:distinct => true)
+ assert_equal 6, Account.includes(:firm).distinct.count
+ assert_equal 4, Account.includes(:firm).distinct.select(:credit_limit).count
end
def test_should_not_perform_joined_include_by_default
@@ -341,7 +341,18 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 5, Account.count(:firm_id)
end
- def test_count_with_uniq
+ def test_count_distinct_option_is_deprecated
+ assert_deprecated do
+ assert_equal 4, Account.select(:credit_limit).count(distinct: true)
+ end
+
+ assert_deprecated do
+ assert_equal 6, Account.select(:credit_limit).count(distinct: false)
+ end
+ end
+
+ def test_count_with_distinct
+ assert_equal 4, Account.select(:credit_limit).distinct.count
assert_equal 4, Account.select(:credit_limit).uniq.count
end
@@ -351,7 +362,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_field_in_joined_table
assert_equal 5, Account.joins(:firm).count('companies.id')
- assert_equal 4, Account.joins(:firm).count('companies.id', :distinct => true)
+ assert_equal 4, Account.joins(:firm).distinct.count('companies.id')
end
def test_should_count_field_in_joined_table_with_group_by
@@ -455,7 +466,7 @@ class CalculationsTest < ActiveRecord::TestCase
approved_topics_count = Topic.group(:approved).count(:author_name)[true]
assert_equal approved_topics_count, 3
# Count the number of distinct authors for approved Topics
- distinct_authors_for_approved_count = Topic.group(:approved).count(:author_name, :distinct => true)[true]
+ distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true]
assert_equal distinct_authors_for_approved_count, 2
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 7457bafd4e..187cad9599 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -520,7 +520,7 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_inheritence_of_callbacks
+ def test_inheritance_of_callbacks
parent = ParentDeveloper.new
assert !parent.after_save_called
parent.save
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index bd2fbaa7db..dbb2f223cd 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def @adapter.native_database_types
{:string => "varchar"}
end
+ @viz = @adapter.schema_creation
end
def test_can_set_coder
@@ -35,25 +36,25 @@ module ActiveRecord
def test_should_not_include_default_clause_when_default_is_null
column = Column.new("title", nil, "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal "title varchar(20)", column_def.to_sql
+ assert_equal "title varchar(20)", @viz.accept(column_def)
end
def test_should_include_default_clause_when_default_is_present
column = Column.new("title", "Hello", "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def)
end
def test_should_specify_not_null_if_null_option_is_false
column = Column.new("title", "Hello", "varchar(20)", false)
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
if current_adapter?(:MysqlAdapter)
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 541e983758..ecad7c942f 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -9,49 +9,46 @@ module ActiveRecord
end
def test_primary_key
- assert_equal 'id', @cache.primary_keys['posts']
+ assert_equal 'id', @cache.primary_keys('posts')
end
def test_primary_key_for_non_existent_table
- assert_nil @cache.primary_keys['omgponies']
+ assert_nil @cache.primary_keys('omgponies')
end
def test_caches_columns
- columns = @cache.columns['posts']
- assert_equal columns, @cache.columns['posts']
+ columns = @cache.columns('posts')
+ assert_equal columns, @cache.columns('posts')
end
def test_caches_columns_hash
- columns_hash = @cache.columns_hash['posts']
- assert_equal columns_hash, @cache.columns_hash['posts']
+ columns_hash = @cache.columns_hash('posts')
+ assert_equal columns_hash, @cache.columns_hash('posts')
end
def test_clearing
- @cache.columns['posts']
- @cache.columns_hash['posts']
- @cache.tables['posts']
- @cache.primary_keys['posts']
+ @cache.columns('posts')
+ @cache.columns_hash('posts')
+ @cache.tables('posts')
+ @cache.primary_keys('posts')
@cache.clear!
- assert_equal 0, @cache.columns.size
- assert_equal 0, @cache.columns_hash.size
- assert_equal 0, @cache.tables.size
- assert_equal 0, @cache.primary_keys.size
+ assert_equal 0, @cache.size
end
def test_dump_and_load
- @cache.columns['posts']
- @cache.columns_hash['posts']
- @cache.tables['posts']
- @cache.primary_keys['posts']
+ @cache.columns('posts')
+ @cache.columns_hash('posts')
+ @cache.tables('posts')
+ @cache.primary_keys('posts')
@cache = Marshal.load(Marshal.dump(@cache))
- assert_equal 12, @cache.columns['posts'].size
- assert_equal 12, @cache.columns_hash['posts'].size
- assert @cache.tables['posts']
- assert_equal 'id', @cache.primary_keys['posts']
+ assert_equal 12, @cache.columns('posts').size
+ assert_equal 12, @cache.columns_hash('posts').size
+ assert @cache.tables('posts')
+ assert_equal 'id', @cache.primary_keys('posts')
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 23e64bee7e..e6af29282c 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -185,7 +185,7 @@ module ActiveRecord
assert_not_nil connection
threads = []
4.times do |i|
- threads << Thread.new(i) do |pool_count|
+ threads << Thread.new(i) do
connection = pool.connection
assert_not_nil connection
connection.close
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index fc46a249c8..ac093251a5 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -115,10 +115,19 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "update other counters on parent destroy" do
+ david, joanna = dog_lovers(:david, :joanna)
+ joanna = joanna # squelch a warning
+
+ assert_difference 'joanna.reload.dogs_count', -1 do
+ david.destroy
+ end
+ end
+
test "reset the right counter if two have the same foreign key" do
michael = people(:michael)
assert_nothing_raised(ActiveRecord::StatementInvalid) do
- Person.reset_counters(michael.id, :followers)
+ Person.reset_counters(michael.id, :friends_too)
end
end
@@ -131,4 +140,11 @@ class CounterCacheTest < ActiveRecord::TestCase
Subscriber.reset_counters(subscriber.id, 'books')
end
end
+
+ test "the passed symbol needs to be an association name" do
+ e = assert_raises(ArgumentError) do
+ Topic.reset_counters(@topic.id, :replies_count)
+ end
+ assert_equal "'Topic' has no association called 'replies_count'", e.message
+ end
end
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index fe105b9d22..f73e449610 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -110,7 +110,7 @@ module ActiveRecord
def test_dup_validity_is_independent
repair_validations(Topic) do
Topic.validates_presence_of :title
- topic = Topic.new("title" => "Litterature")
+ topic = Topic.new("title" => "Literature")
topic.valid?
duped = topic.dup
@@ -128,7 +128,7 @@ module ActiveRecord
prev_default_scopes = Topic.default_scopes
Topic.default_scopes = [Topic.where(:approved => true)]
topic = Topic.new(:approved => false)
- assert !topic.dup.approved?, "should not be overriden by default scopes"
+ assert !topic.dup.approved?, "should not be overridden by default scopes"
ensure
Topic.default_scopes = prev_default_scopes
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index a9fa107749..e505fe9f18 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -82,7 +82,7 @@ class FinderTest < ActiveRecord::TestCase
# ensures +exists?+ runs valid SQL by excluding order value
def test_exists_with_order
- assert Topic.order(:id).uniq.exists?
+ assert Topic.order(:id).distinct.exists?
end
def test_exists_with_includes_limit_and_empty_result
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index b0b29f5f42..f6cfee0cb8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -477,9 +477,8 @@ class CustomConnectionFixturesTest < ActiveRecord::TestCase
fixtures :courses
self.use_transactional_fixtures = false
- def test_connection
- assert_kind_of Course, courses(:ruby)
- assert_equal Course.connection, courses(:ruby).connection
+ def test_connection_instance_method_deprecation
+ assert_deprecated { courses(:ruby).connection }
end
def test_leaky_destroy
@@ -577,6 +576,15 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
end
end
+class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
+ self.fixture_path = Pathname.new(FIXTURES_ROOT).join('all')
+ fixtures :all
+
+ def test_all_there
+ assert_equal %w(developers people tasks), fixture_table_names.sort
+ end
+end
+
class FasterFixturesTest < ActiveRecord::TestCase
fixtures :categories, :authors
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index b91146db4e..99d54e7526 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -68,6 +68,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_company_descends_from_active_record
+ assert !ActiveRecord::Base.descends_from_active_record?
assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base'
assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
@@ -171,6 +172,20 @@ class InheritanceTest < ActiveRecord::TestCase
assert_equal Firm, firm.class
end
+ def test_new_with_abstract_class
+ e = assert_raises(NotImplementedError) do
+ AbstractCompany.new
+ end
+ assert_equal("AbstractCompany is an abstract class and can not 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)
+ end
+
def test_new_with_invalid_type
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') }
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index a0a3e6cb0d..77891b9156 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -193,11 +193,19 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_lock_without_default_sets_version_to_zero
t1 = LockWithoutDefault.new
assert_equal 0, t1.lock_version
+
+ t1.save
+ t1 = LockWithoutDefault.find(t1.id)
+ assert_equal 0, t1.lock_version
end
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
assert_equal 0, t1.custom_lock_version
+
+ t1.save
+ t1 = LockWithCustomColumnWithoutDefault.find(t1.id)
+ assert_equal 0, t1.custom_lock_version
end
def test_readonly_attributes
@@ -341,9 +349,6 @@ end
# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
-
-# TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
-
unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
class PessimisticLockingTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index e52809f0f8..2d7a7ec73a 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -55,13 +55,20 @@ module ActiveRecord
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
assert_equal 70000, default_before
- rename_column "test_models", "salary", "anual_salary"
+ rename_column "test_models", "salary", "annual_salary"
- assert TestModel.column_names.include?("anual_salary")
- default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default
+ assert TestModel.column_names.include?("annual_salary")
+ default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
assert_equal 70000, default_after
end
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ def test_mysql_rename_column_preserves_auto_increment
+ rename_column "test_models", "id", "id_test"
+ assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra
+ end
+ end
+
def test_rename_nonexistent_column
exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
ActiveRecord::StatementInvalid
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index b593270352..1eb3bc4177 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -271,6 +271,19 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal 'lifo', topic.author_name
end
+ # Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/
+ # has been done by evaluating a string with a plain def statement. For scope
+ # names which contain spaces this approach doesn't work.
+ def test_spaces_in_scope_names
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ scope :"title containing space", -> { where("title LIKE '% %'") }
+ scope :approved, -> { where(:approved => true) }
+ end
+ assert_equal klass.send(:"title containing space"), klass.where("title LIKE '% %'")
+ assert_equal klass.approved.send(:"title containing space"), klass.approved.where("title LIKE '% %'")
+ end
+
def test_find_all_should_behave_like_select
assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved)
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 94837341fc..b6e140b912 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -167,7 +167,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
Man.accepts_nested_attributes_for(:interests)
man = Man.create(:name => "John")
- interest = man.interests.create :topic => 'gardning'
+ interest = man.interests.create :topic => 'gardening'
man = Man.find man.id
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
@@ -806,7 +806,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(NoMethodError) { @pirate.save! }
end
- def test_numeric_colum_changes_from_zero_to_no_empty_string
+ def test_numeric_column_changes_from_zero_to_no_empty_string
Man.accepts_nested_attributes_for(:interests)
repair_validations(Interest) do
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b936cca875..572431ee87 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -247,15 +247,15 @@ class PersistencesTest < ActiveRecord::TestCase
topic.title = "Another New Topic"
topic.written_on = "2003-12-12 23:23:00"
topic.save
- topicReloaded = Topic.find(topic.id)
- assert_equal("Another New Topic", topicReloaded.title)
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topic_reloaded.title)
- topicReloaded.title = "Updated topic"
- topicReloaded.save
+ topic_reloaded.title = "Updated topic"
+ topic_reloaded.save
- topicReloadedAgain = Topic.find(topic.id)
+ topic_reloaded_again = Topic.find(topic.id)
- assert_equal("Updated topic", topicReloadedAgain.title)
+ assert_equal("Updated topic", topic_reloaded_again.title)
end
def test_update_columns_not_equal_attributes
@@ -263,12 +263,12 @@ class PersistencesTest < ActiveRecord::TestCase
topic.title = "Still another topic"
topic.save
- topicReloaded = Topic.allocate
- topicReloaded.init_with(
+ topic_reloaded = Topic.allocate
+ topic_reloaded.init_with(
'attributes' => topic.attributes.merge('does_not_exist' => 'test')
)
- topicReloaded.title = 'A New Topic'
- assert_nothing_raised { topicReloaded.save }
+ topic_reloaded.title = 'A New Topic'
+ assert_nothing_raised { topic_reloaded.save }
end
def test_update_for_record_with_only_primary_key
@@ -296,6 +296,22 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal "Reply", topic.type
end
+ def test_update_after_create
+ klass = Class.new(Topic) do
+ def self.name; 'Topic'; end
+ after_create do
+ update_attribute("author_name", "David")
+ end
+ end
+ topic = klass.new
+ topic.title = "Another New Topic"
+ topic.save
+
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topic_reloaded.title)
+ assert_equal("David", topic_reloaded.author_name)
+ end
+
def test_delete
topic = Topic.find(1)
assert_equal topic, topic.delete, 'topic.delete did not return self'
@@ -661,6 +677,15 @@ class PersistencesTest < ActiveRecord::TestCase
topic.reload
assert !topic.approved?
assert_equal "The First Topic", topic.title
+
+ assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
+ topic.update_attributes(id: 3, title: "Hm is it possible?")
+ end
+ assert_not_equal "Hm is it possible?", Topic.find(3).title
+
+ topic.update_attributes(id: 1234)
+ assert_nothing_raised { topic.reload }
+ assert_equal topic.title, Topic.find(1234).title
end
def test_update!
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 8ce44636b4..92d1e013e8 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -6,26 +6,31 @@ module ActiveRecord
class WhereChainTest < ActiveRecord::TestCase
fixtures :posts
+ def setup
+ super
+ @name = 'title'
+ end
+
def test_not_eq
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
relation = Post.where.not(title: 'hello')
assert_equal([expected], relation.where_values)
end
def test_not_null
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], nil)
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], nil)
relation = Post.where.not(title: nil)
assert_equal([expected], relation.where_values)
end
def test_not_in
- expected = Arel::Nodes::NotIn.new(Post.arel_table[:title], %w[hello goodbye])
+ expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
assert_equal([expected], relation.where_values)
end
def test_association_not_eq
- expected = Arel::Nodes::NotEqual.new(Comment.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Comment.arel_table[@name], 'hello')
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
@@ -33,20 +38,20 @@ module ActiveRecord
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
- expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'world')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'world')
assert_equal(expected, relation.where_values.last)
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'world')
+ expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'world')
assert_equal(expected, relation.where_values.last)
end
@@ -65,10 +70,10 @@ module ActiveRecord
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
- expected = Arel::Nodes::NotIn.new(Post.arel_table[:author_id], [1, 2])
+ expected = Arel::Nodes::NotIn.new(Post.arel_table['author_id'], [1, 2])
assert_equal(expected, relation.where_values[0])
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'ruby on rails')
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'ruby on rails')
assert_equal(expected, relation.where_values[1])
end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 2b4aadc7ed..e2af66ff9d 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -293,7 +293,7 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal [magician], people(:michael).bad_references
end
- def test_should_default_scope_on_associations_is_overriden_by_association_conditions
+ def test_should_default_scope_on_associations_is_overridden_by_association_conditions
reference = references(:michael_unicyclist).becomes(BadReference)
assert_equal [reference], people(:michael).fixed_bad_references
end
@@ -390,20 +390,20 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scope_with_inheritance
wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_default_scope_with_module_includes
wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash
- assert_equal "Jamis", wheres[:name]
- assert_equal 50000, wheres[:salary]
+ assert_equal "Jamis", wheres['name']
+ assert_equal 50000, wheres['salary']
end
def test_scope_overwrites_default
@@ -563,7 +563,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
Developer.select("id").unscope("select")
end
- assert_raises(ArgumentError) do
+ assert_raises(ArgumentError) do
Developer.select("id").unscope(5)
end
end
@@ -634,7 +634,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+
+ assert_equal 11, DeveloperCalledJamis.unscoped.length
+ assert_equal 1, DeveloperCalledJamis.poor.length
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
+ assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
def test_default_scope_select_ignored_by_aggregations
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index fd0b05cb77..9ca980fdf6 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -278,5 +278,17 @@ module ActiveRecord
assert_equal [NullRelation], relation.extending_values
assert relation.is_a?(NullRelation)
end
+
+ test "distinct!" do
+ relation.distinct! :foo
+ assert_equal :foo, relation.distinct_value
+ assert_equal :foo, relation.uniq_value # deprecated access
+ end
+
+ test "uniq! was replaced by distinct!" do
+ relation.uniq! :foo
+ assert_equal :foo, relation.distinct_value
+ assert_equal :foo, relation.uniq_value # deprecated access
+ end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 26cbb03892..9008c2785e 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -492,6 +492,7 @@ class RelationTest < ActiveRecord::TestCase
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
+ assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
end
@@ -789,11 +790,11 @@ class RelationTest < ActiveRecord::TestCase
def test_count_with_distinct
posts = Post.all
- assert_equal 3, posts.count(:comments_count, :distinct => true)
- assert_equal 11, posts.count(:comments_count, :distinct => false)
+ assert_equal 3, posts.distinct(true).count(:comments_count)
+ assert_equal 11, posts.distinct(false).count(:comments_count)
- assert_equal 3, posts.select(:comments_count).count(:distinct => true)
- assert_equal 11, posts.select(:comments_count).count(:distinct => false)
+ assert_equal 3, posts.distinct(true).select(:comments_count).count
+ assert_equal 11, posts.distinct(false).select(:comments_count).count
end
def test_count_explicit_columns
@@ -1223,6 +1224,16 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_turn_off_eager_loading_with_conditions_on_joins
+ original_value = ActiveRecord::Base.disable_implicit_join_references
+ ActiveRecord::Base.disable_implicit_join_references = true
+
+ scope = Topic.where(author_email_address: 'my.example@gmail.com').includes(:replies)
+ assert_not scope.eager_loading?
+ ensure
+ ActiveRecord::Base.disable_implicit_join_references = original_value
+ end
+
def test_ordering_with_extra_spaces
assert_equal authors(:david), Author.order('id DESC , name DESC').last
end
@@ -1269,7 +1280,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), comments(:greetings).post
end
- def test_uniq
+ def test_distinct
tag1 = Tag.create(:name => 'Foo')
tag2 = Tag.create(:name => 'Foo')
@@ -1277,11 +1288,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['Foo', 'Foo'], query.map(&:name)
assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.distinct.map(&:name)
assert_equal ['Foo'], query.uniq.map(&:name)
end
assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.distinct(true).map(&:name)
assert_equal ['Foo'], query.uniq(true).map(&:name)
end
+ assert_equal ['Foo', 'Foo'], query.distinct(true).distinct(false).map(&:name)
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1147b9a09e..6e03a2bd67 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,13 +177,19 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) or current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ else
+ assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ end
end
def test_schema_dumps_partial_indices
index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition
+ assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
end
@@ -219,6 +225,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
end
+
+ def test_schema_dumps_index_type
+ output = standard_dump
+ assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
+ assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ end
end
def test_schema_dump_includes_decimal_options
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index dadcca5b7f..816bd62751 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -71,7 +71,7 @@ module ActiveRecord
return skip("only tested on mysql")
end
- @connection = stub(:create_database => true, :execute => true)
+ @connection = stub("Connection", create_database: true)
@error = Mysql::Error.new "Invalid permissions"
@configuration = {
'adapter' => 'mysql',
@@ -90,6 +90,7 @@ module ActiveRecord
end
def test_root_password_is_requested
+ assert_permissions_granted_for "pat"
skip "only if mysql is available" unless defined?(::Mysql)
$stdin.expects(:gets).returns("secret\n")
@@ -97,6 +98,7 @@ module ActiveRecord
end
def test_connection_established_as_root
+ assert_permissions_granted_for "pat"
ActiveRecord::Base.expects(:establish_connection).with(
'adapter' => 'mysql',
'database' => nil,
@@ -108,6 +110,7 @@ module ActiveRecord
end
def test_database_created_by_root
+ assert_permissions_granted_for "pat"
@connection.expects(:create_database).
with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
@@ -115,12 +118,18 @@ module ActiveRecord
end
def test_grant_privileges_for_normal_user
- @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;")
+ assert_permissions_granted_for "pat"
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ def test_do_not_grant_privileges_for_root_user
+ @configuration['username'] = 'root'
+ @configuration['password'] = ''
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
def test_connection_established_as_normal_user
+ assert_permissions_granted_for "pat"
ActiveRecord::Base.expects(:establish_connection).returns do
ActiveRecord::Base.expects(:establish_connection).with(
'adapter' => 'mysql',
@@ -142,6 +151,13 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
+
+ private
+ def assert_permissions_granted_for(db_user)
+ db_name = @configuration['database']
+ db_password = @configuration['password']
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
+ end
end
class MySQLDBDropTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index eb4ffd4498..766a5c0c90 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -182,9 +182,9 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_rollback_when_commit_fails
- @first.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
+ @first.class.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
begin
- @first.connection.class.class_eval do
+ @first.class.connection.class.class_eval do
def commit_db_transaction; raise "boom!"; end
end
@@ -194,8 +194,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert !@first.save rescue nil
assert_equal [:after_rollback], @first.history
ensure
- @first.connection.class.send(:remove_method, :commit_db_transaction)
- @first.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ @first.class.connection.class.send(:remove_method, :commit_db_transaction)
+ @first.class.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 46e767af1a..57457359b1 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -54,7 +54,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
- t2.title = "Now Im really also unique"
+ t2.title = "Now I am really also unique"
assert t2.save, "Should now save t2 as unique"
end
@@ -348,7 +348,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_conditions
- Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true))
+ Topic.validates_uniqueness_of :title, conditions: -> { where(approved: true) }
Topic.create("title" => "I'm a topic", "approved" => true)
Topic.create("title" => "I'm an unapproved topic", "approved" => false)
@@ -359,6 +359,12 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t4.valid?, "t4 should be valid"
end
+ def test_validate_uniqueness_with_non_callable_conditions_is_not_supported
+ assert_raises(ArgumentError) {
+ Topic.validates_uniqueness_of :title, conditions: Topic.where(approved: true)
+ }
+ end
+
def test_validate_uniqueness_with_array_column
return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index 11912ca1cc..c02b3241cd 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def repair_validations(*model_classes)
teardown do
model_classes.each do |k|
- k.reset_callbacks(:validate)
+ k.clear_validators!
end
end
end
@@ -16,7 +16,7 @@ module ActiveRecord
yield
ensure
model_classes.each do |k|
- k.reset_callbacks(:validate)
+ k.clear_validators!
end
end
end
diff --git a/activerecord/test/fixtures/dog_lovers.yml b/activerecord/test/fixtures/dog_lovers.yml
index d3e5e4a1aa..3f4c6c9e4c 100644
--- a/activerecord/test/fixtures/dog_lovers.yml
+++ b/activerecord/test/fixtures/dog_lovers.yml
@@ -2,3 +2,6 @@ david:
id: 1
bred_dogs_count: 0
trained_dogs_count: 1
+joanna:
+ id: 2
+ dogs_count: 1
diff --git a/activerecord/test/fixtures/dogs.yml b/activerecord/test/fixtures/dogs.yml
index 16d19be2c5..b5eb2c7b74 100644
--- a/activerecord/test/fixtures/dogs.yml
+++ b/activerecord/test/fixtures/dogs.yml
@@ -1,3 +1,4 @@
sophie:
id: 1
trainer_id: 1
+ dog_lover_id: 2
diff --git a/activerecord/test/fixtures/friendships.yml b/activerecord/test/fixtures/friendships.yml
index 1ee09175bf..ae0abe0162 100644
--- a/activerecord/test/fixtures/friendships.yml
+++ b/activerecord/test/fixtures/friendships.yml
@@ -1,4 +1,4 @@
Connection 1:
id: 1
- person_id: 1
- friend_id: 2 \ No newline at end of file
+ friend_id: 1
+ follower_id: 2
diff --git a/activerecord/test/fixtures/people.yml b/activerecord/test/fixtures/people.yml
index e640a38f1f..0ec05e8d56 100644
--- a/activerecord/test/fixtures/people.yml
+++ b/activerecord/test/fixtures/people.yml
@@ -5,6 +5,7 @@ michael:
number1_fan_id: 3
gender: M
followers_count: 1
+ friends_too_count: 1
david:
id: 2
first_name: David
@@ -12,6 +13,7 @@ david:
number1_fan_id: 1
gender: M
followers_count: 1
+ friends_too_count: 1
susan:
id: 3
first_name: Susan
@@ -19,3 +21,4 @@ susan:
number1_fan_id: 1
gender: F
followers_count: 1
+ friends_too_count: 1
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 8423411474..a96899ae10 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -30,8 +30,8 @@ class Author < ActiveRecord::Base
has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments
has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments
has_many :funky_comments, :through => :posts, :source => :comments
- has_many :ordered_uniq_comments, -> { uniq.order('comments.id') }, :through => :posts, :source => :comments
- has_many :ordered_uniq_comments_desc, -> { uniq.order('comments.id DESC') }, :through => :posts, :source => :comments
+ has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments
+ has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments
has_many :readonly_comments, -> { readonly }, :through => :posts, :source => :comments
has_many :special_posts
@@ -78,7 +78,7 @@ class Author < ActiveRecord::Base
has_many :categories_like_general, -> { where(:name => 'General') }, :through => :categorizations, :source => :category, :class_name => 'Category'
has_many :categorized_posts, :through => :categorizations, :source => :post
- has_many :unique_categorized_posts, -> { uniq }, :through => :categorizations, :source => :post
+ has_many :unique_categorized_posts, -> { distinct }, :through => :categorizations, :source => :post
has_many :nothings, :through => :kateggorisatons, :class_name => 'Category'
@@ -91,7 +91,7 @@ class Author < ActiveRecord::Base
has_many :post_categories, :through => :posts, :source => :categories
has_many :tagging_tags, :through => :taggings, :source => :tag
- has_many :similar_posts, -> { uniq }, :through => :tags, :source => :tagged_posts
+ has_many :similar_posts, -> { distinct }, :through => :tags, :source => :tagged_posts
has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, :through => :posts, :source => :tags
has_many :tags_with_primary_key, :through => :posts
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index ce81a37966..5458a28cc9 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -2,7 +2,7 @@ class Book < ActiveRecord::Base
has_many :authors
has_many :citations, :foreign_key => 'book1_id'
- has_many :references, -> { uniq }, :through => :citations, :source => :reference_of
+ has_many :references, -> { distinct }, :through => :citations, :source => :reference_of
has_many :subscriptions
has_many :subscribers, :through => :subscriptions
diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb
index 72b7d33a86..b02b8447b8 100644
--- a/activerecord/test/models/dog.rb
+++ b/activerecord/test/models/dog.rb
@@ -1,4 +1,5 @@
class Dog < ActiveRecord::Base
- belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count
- belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count
+ belongs_to :breeder, class_name: "DogLover", counter_cache: :bred_dogs_count
+ belongs_to :trainer, class_name: "DogLover", counter_cache: :trained_dogs_count
+ belongs_to :doglover, foreign_key: :dog_lover_id, class_name: "DogLover", counter_cache: true
end
diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb
index a33dc575c5..2c5be94aea 100644
--- a/activerecord/test/models/dog_lover.rb
+++ b/activerecord/test/models/dog_lover.rb
@@ -1,4 +1,5 @@
class DogLover < ActiveRecord::Base
- has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id
- has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id
+ has_many :trained_dogs, class_name: "Dog", foreign_key: :trainer_id, dependent: :destroy
+ has_many :bred_dogs, class_name: "Dog", foreign_key: :breeder_id
+ has_many :dogs
end
diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb
index 6b4f7acc38..4b411ca8e0 100644
--- a/activerecord/test/models/friendship.rb
+++ b/activerecord/test/models/friendship.rb
@@ -1,4 +1,6 @@
class Friendship < ActiveRecord::Base
belongs_to :friend, class_name: 'Person'
- belongs_to :follower, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :followers_count
+ # friend_too exists to test a bug, and probably shouldn't be used elsewhere
+ belongs_to :friend_too, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :friends_too_count
+ belongs_to :follower, class_name: 'Person'
end
diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb
index 6cfd443e75..69d4d7df1a 100644
--- a/activerecord/test/models/liquid.rb
+++ b/activerecord/test/models/liquid.rb
@@ -1,5 +1,4 @@
class Liquid < ActiveRecord::Base
self.table_name = :liquid
- has_many :molecules, -> { uniq }
+ has_many :molecules, -> { distinct }
end
-
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index fa717ef8d6..2985160d28 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -8,7 +8,10 @@ class Person < ActiveRecord::Base
has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) },
:through => :readers, :source => :post
- has_many :followers, foreign_key: 'friend_id', class_name: 'Friendship'
+ has_many :friendships, foreign_key: 'friend_id'
+ # friends_too exists to test a bug, and probably shouldn't be used elsewhere
+ has_many :friends_too, foreign_key: 'friend_id', class_name: 'Friendship'
+ has_many :followers, through: :friendships
has_many :references
has_many :bad_references
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 90273adafc..f893754b9f 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -1,11 +1,11 @@
class Project < ActiveRecord::Base
- has_and_belongs_to_many :developers, -> { uniq.order 'developers.name desc, developers.id desc' }
+ has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' }
has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer"
- has_and_belongs_to_many :selected_developers, -> { uniq.select "developers.*" }, :class_name => "Developer"
+ has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer"
has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer'
has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer"
- has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").uniq }, :class_name => "Developer"
- has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').uniq }, :class_name => "Developer"
+ has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer"
+ has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').distinct }, :class_name => "Developer"
has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, :class_name => "Developer"
ActiveSupport::Deprecation.silence do
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index f25f72c481..1b1457ab9c 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -14,6 +14,16 @@ ActiveRecord::Schema.define do
add_index :binary_fields, :var_binary
+ create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ t.string :awesome
+ t.string :pizza
+ t.string :snacks
+ end
+
+ add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
+ add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
+ add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index 5401c12ed5..ecdce1519b 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -14,6 +14,16 @@ ActiveRecord::Schema.define do
add_index :binary_fields, :var_binary
+ create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ t.string :awesome
+ t.string :pizza
+ t.string :snacks
+ end
+
+ add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
+ add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
+ add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ae779c702a..8beb58f3fc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -183,6 +183,7 @@ ActiveRecord::Schema.define do
add_index :companies, [:firm_id, :type, :rating], :name => "company_index"
add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10"
+ add_index :companies, :name, :name => 'company_name_index', :using => :btree
create_table :vegetables, :force => true do |t|
t.string :name
@@ -230,14 +231,16 @@ ActiveRecord::Schema.define do
t.integer :access_level, :default => 1
end
- create_table :dog_lovers, :force => true do |t|
- t.integer :trained_dogs_count, :default => 0
- t.integer :bred_dogs_count, :default => 0
+ create_table :dog_lovers, force: true do |t|
+ t.integer :trained_dogs_count, default: 0
+ t.integer :bred_dogs_count, default: 0
+ t.integer :dogs_count, default: 0
end
create_table :dogs, :force => true do |t|
t.integer :trainer_id
t.integer :breeder_id
+ t.integer :dog_lover_id
end
create_table :edges, :force => true, :id => false do |t|
@@ -280,7 +283,7 @@ ActiveRecord::Schema.define do
create_table :friendships, :force => true do |t|
t.integer :friend_id
- t.integer :person_id
+ t.integer :follower_id
end
create_table :goofy_string_id, :force => true, :id => false do |t|
@@ -494,6 +497,7 @@ ActiveRecord::Schema.define do
t.integer :lock_version, :null => false, :default => 0
t.string :comments
t.integer :followers_count, :default => 0
+ t.integer :friends_too_count, :default => 0
t.references :best_friend
t.references :best_friend_of
t.integer :insures, null: false, default: 0
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 9d26b8ba3e..08ec528419 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,9 +1,20 @@
## Rails 4.0.0 (unreleased) ##
-* Fix deletion of empty directories in ActiveSupport::Cache::FileStore.
+* `ActiveSupport::Notifications::Instrumenter#instrument` should yield
+ its payload.
+
+ *stopdropandrew*
+
+* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context.
+ Fixes #9772.
+
+ *Yves Senn*
+
+* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`.
*Charles Jones*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
* Improve singularizing a singular for multiple cases.
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 822c9d98ae..e3788ed54f 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -15,8 +15,6 @@ namespace :test do
end
end
-# Create compressed packages
-dist_dirs = [ "lib", "test"]
spec = eval(File.read('activesupport.gemspec'))
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index a28310032a..b46a331f6a 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
s.add_dependency 'multi_json', '~> 1.3'
- s.add_dependency 'tzinfo', '~> 0.3.33'
+ s.add_dependency 'tzinfo', '~> 0.3.37'
s.add_dependency 'minitest', '~> 4.2'
s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index edbe697962..6bfac15289 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -344,7 +344,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def write(name, value, options = nil)
options = merged_options(options)
- instrument(:write, name, options) do |payload|
+ instrument(:write, name, options) do
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
end
@@ -355,7 +355,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
- instrument(:delete, name) do |payload|
+ instrument(:delete, name) do
delete_entry(namespaced_key(name, options), options)
end
end
@@ -365,7 +365,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
- instrument(:exist?, name) do |payload|
+ instrument(:exist?, name) do
entry = read_entry(namespaced_key(name, options), options)
entry && !entry.expired?
end
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index b48bdf08e8..998a59c618 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,4 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
+Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
next if File.basename(path, '.rb') == 'logger'
- require "active_support/core_ext/#{File.basename(path, '.rb')}"
+ require path
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 5d8d09aa69..e51ab9ddbc 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -73,42 +73,44 @@ class Class
instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
- # We use class_eval here rather than define_method because class_attribute
- # may be used in a performance sensitive context therefore the overhead that
- # define_method introduces may become significant.
attrs.each do |name|
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def self.#{name}() nil end
- def self.#{name}?() !!#{name} end
+ define_singleton_method(name) { nil }
+ define_singleton_method("#{name}?") { !!public_send(name) }
- def self.#{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
+ ivar = "@#{name}"
+
+ define_singleton_method("#{name}=") do |val|
+ singleton_class.class_eval do
+ remove_possible_method(name)
+ define_method(name) { val }
+ end
- if singleton_class?
- class_eval do
- remove_possible_method(:#{name})
- def #{name}
- defined?(@#{name}) ? @#{name} : singleton_class.#{name}
+ if singleton_class?
+ class_eval do
+ remove_possible_method(name)
+ define_method(name) do
+ if instance_variable_defined? ivar
+ instance_variable_get ivar
+ else
+ singleton_class.send name
end
end
end
- val
end
+ val
+ end
- if instance_reader
- remove_possible_method :#{name}
- def #{name}
- defined?(@#{name}) ? @#{name} : self.class.#{name}
- end
-
- def #{name}?
- !!#{name}
+ if instance_reader
+ remove_possible_method name
+ define_method(name) do
+ if instance_variable_defined?(ivar)
+ instance_variable_get ivar
+ else
+ self.class.public_send name
end
end
- RUBY
+ define_method("#{name}?") { !!public_send(name) }
+ end
attr_writer name if instance_writer
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index a87383fe99..e0cd92ae3c 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -19,10 +19,10 @@ module ActiveSupport
# end
#
# By default it uses Marshal to serialize the message. If you want to use
- # another serialization method, you can set the serializer attribute to
- # something that responds to dump and load, e.g.:
+ # another serialization method, you can set the serializer in the options
+ # hash upon initialization:
#
- # @verifier.serializer = YAML
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
class MessageVerifier
class InvalidSignature < StandardError; end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 4f1cb85cc2..0c9a729ce5 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -17,7 +17,7 @@ module ActiveSupport
def instrument(name, payload={})
start name, payload
begin
- yield
+ yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
raise e
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 52bfeb7179..1b2a75c35d 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -38,6 +38,8 @@ module ActiveSupport
begin
constant = names.join("::").constantize
break(constant) if yield(constant)
+ rescue NoMethodError # subclass of NameError
+ raise
rescue NameError
# Constant wasn't found, move on
ensure
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 98c866ac43..4a032b0ad0 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -366,6 +366,8 @@ module ActiveSupport
# TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
wrap_with_time_zone time.__send__(sym, *args, &block)
+ rescue NoMethodError => e
+ raise e, e.message.sub(time.inspect, self.inspect), e.backtrace
end
private
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index c5fbddcb5f..21a0620c22 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -62,6 +62,7 @@ module ActiveSupport
"Newfoundland" => "America/St_Johns",
"Brasilia" => "America/Sao_Paulo",
"Buenos Aires" => "America/Argentina/Buenos_Aires",
+ "Montevideo" => "America/Montevideo",
"Georgetown" => "America/Guyana",
"Greenland" => "America/Godthab",
"Mid-Atlantic" => "Atlantic/South_Georgia",
@@ -150,7 +151,7 @@ module ActiveSupport
"Taipei" => "Asia/Taipei",
"Perth" => "Australia/Perth",
"Irkutsk" => "Asia/Irkutsk",
- "Ulaan Bataar" => "Asia/Ulaanbaatar",
+ "Ulaanbaatar" => "Asia/Ulaanbaatar",
"Seoul" => "Asia/Seoul",
"Osaka" => "Asia/Tokyo",
"Sapporo" => "Asia/Tokyo",
@@ -238,7 +239,7 @@ module ActiveSupport
# Compare #name and TZInfo identifier to a supplied regexp, returning +true+
# if a match is found.
def =~(re)
- return true if name =~ re || MAPPING[name] =~ re
+ re === name || re === MAPPING[name]
end
# Returns a textual representation of this time zone.
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index ec0967fdd7..ca23057189 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,10 +1,11 @@
module ActiveSupport
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveSupport as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveSupport.version.segments
+ STRING = ActiveSupport.version.to_s
end
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 4551dd2f2d..27c64c4dca 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -37,6 +37,12 @@ module ActiveSupport
{}
else
@dbf = DocumentBuilderFactory.new_instance
+ # secure processing of java xml
+ # http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html
+ @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
+ @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false)
+ @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
+ @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true)
xml_string_reader = StringReader.new(data)
xml_input_source = InputSource.new(xml_string_reader)
doc = @dbf.new_document_builder.parse(xml_input_source)
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index ede08e23d5..acd320dbe0 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1,7 +1,7 @@
require 'logger'
require 'abstract_unit'
require 'active_support/cache'
-require 'dependecies_test_helpers'
+require 'dependencies_test_helpers'
class CacheKeyTest < ActiveSupport::TestCase
def test_entry_legacy_optional_ivars
@@ -564,7 +564,7 @@ module LocalCacheBehavior
end
module AutoloadingCacheBehavior
- include DependeciesTestHelpers
+ include DependenciesTestHelpers
def test_simple_autoloading
with_autoloading_fixtures do
@cache.write('foo', E.new)
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index ac79b15fa8..8f3f710dfd 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -1,10 +1,10 @@
require 'abstract_unit'
require 'active_support/core_ext/marshal'
-require 'dependecies_test_helpers'
+require 'dependencies_test_helpers'
class MarshalTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
- include DependeciesTestHelpers
+ include DependenciesTestHelpers
def teardown
ActiveSupport::Dependencies.clear
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 8c7d00cae1..3744d50864 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -153,22 +153,16 @@ end
class NumericExtSizeTest < ActiveSupport::TestCase
def test_unit_in_terms_of_another
- relationships = {
- 1024.bytes => 1.kilobyte,
- 1024.kilobytes => 1.megabyte,
- 3584.0.kilobytes => 3.5.megabytes,
- 3584.0.megabytes => 3.5.gigabytes,
- 1.kilobyte ** 4 => 1.terabyte,
- 1024.kilobytes + 2.megabytes => 3.megabytes,
- 2.gigabytes / 4 => 512.megabytes,
- 256.megabytes * 20 + 5.gigabytes => 10.gigabytes,
- 1.kilobyte ** 5 => 1.petabyte,
- 1.kilobyte ** 6 => 1.exabyte
- }
-
- relationships.each do |left, right|
- assert_equal right, left
- end
+ assert_equal 1024.bytes, 1.kilobyte
+ assert_equal 1024.kilobytes, 1.megabyte
+ assert_equal 3584.0.kilobytes, 3.5.megabytes
+ assert_equal 3584.0.megabytes, 3.5.gigabytes
+ assert_equal 1.kilobyte ** 4, 1.terabyte
+ assert_equal 1024.kilobytes + 2.megabytes, 3.megabytes
+ assert_equal 2.gigabytes / 4, 512.megabytes
+ assert_equal 256.megabytes * 20 + 5.gigabytes, 10.gigabytes
+ assert_equal 1.kilobyte ** 5, 1.petabyte
+ assert_equal 1.kilobyte ** 6, 1.exabyte
end
def test_units_as_bytes_independently
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 8d1a8c628c..92f996f9a4 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -47,7 +47,7 @@ class ToQueryTest < ActiveSupport::TestCase
end
private
- def assert_query_equal(expected, actual, message = nil)
+ def assert_query_equal(expected, actual)
assert_equal expected.split('&'), actual.to_query.split('&')
end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index bff155f045..62c5741ffb 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -293,16 +293,16 @@ end
class StringConversionsTest < ActiveSupport::TestCase
def test_string_to_time
- with_env_tz "US/Eastern" do
+ with_env_tz "Europe/Moscow" do
assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:utc)
assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time
assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:utc)
assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time
assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:utc)
assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time
- assert_equal Time.local(2011, 2, 27, 18, 50), "2011-02-27 22:50 -0100".to_time
+ assert_equal Time.local(2011, 2, 27, 18, 50), "2011-02-27 13:50 -0100".to_time
assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc)
- assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50 -0500".to_time
+ assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 14:50 -0500".to_time
assert_nil "".to_time
end
end
@@ -317,11 +317,11 @@ class StringConversionsTest < ActiveSupport::TestCase
end
def test_partial_string_to_time
- with_env_tz "US/Eastern" do
+ with_env_tz "Europe/Moscow" do
now = Time.now
assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc)
- assert_equal Time.local(now.year, now.month, now.day, 18, 50), "22:50 -0100".to_time
+ assert_equal Time.local(now.year, now.month, now.day, 18, 50), "13:50 -0100".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc)
end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 0f5699fd63..98a87ab9e6 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -779,6 +779,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect
end
+ def test_no_method_error_has_proper_context
+ e = assert_raises(NoMethodError) {
+ @twz.this_method_does_not_exist
+ }
+ assert_equal "undefined method `this_method_does_not_exist' for Fri, 31 Dec 1999 19:00:00 EST -05:00:Time", e.message
+ assert_no_match "rescue", e.backtrace.first
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 615808090d..115a4e894d 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'pp'
require 'active_support/dependencies'
-require 'dependecies_test_helpers'
+require 'dependencies_test_helpers'
module ModuleWithMissing
mattr_accessor :missing_count
@@ -20,7 +20,7 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
end
- include DependeciesTestHelpers
+ include DependenciesTestHelpers
def test_depend_on_path
skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
@@ -878,7 +878,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_autoload_doesnt_shadow_name_error
with_autoloading_fixtures do
Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
- 2.times do |i|
+ 2.times do
begin
::RaisesNameError::FooBarBaz.object_id
flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
diff --git a/activesupport/test/dependecies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb
index 4b46d32fb4..9268512a97 100644
--- a/activesupport/test/dependecies_test_helpers.rb
+++ b/activesupport/test/dependencies_test_helpers.rb
@@ -1,4 +1,4 @@
-module DependeciesTestHelpers
+module DependenciesTestHelpers
def with_loading(*from)
old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
this_dir = File.dirname(__FILE__)
diff --git a/activesupport/test/fixtures/xml/jdom_doctype.dtd b/activesupport/test/fixtures/xml/jdom_doctype.dtd
new file mode 100644
index 0000000000..89480496ef
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_doctype.dtd
@@ -0,0 +1 @@
+<!ENTITY a "external entity">
diff --git a/activesupport/test/fixtures/xml/jdom_entities.txt b/activesupport/test/fixtures/xml/jdom_entities.txt
new file mode 100644
index 0000000000..0337fdaa08
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_entities.txt
@@ -0,0 +1 @@
+<!ENTITY a "hello">
diff --git a/activesupport/test/fixtures/xml/jdom_include.txt b/activesupport/test/fixtures/xml/jdom_include.txt
new file mode 100644
index 0000000000..239ca3afaf
--- /dev/null
+++ b/activesupport/test/fixtures/xml/jdom_include.txt
@@ -0,0 +1 @@
+include me
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index a1e5db6a2e..22cb61ffd6 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -61,9 +61,7 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal(plural, ActiveSupport::Inflector.pluralize(plural))
assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(plural.capitalize))
end
- end
- SingularToPlural.each do |singular, plural|
define_method "test_singularize_singular_#{singular}" do
assert_equal(singular, ActiveSupport::Inflector.singularize(singular))
assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(singular.capitalize))
@@ -326,7 +324,7 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_underscore_as_reverse_of_dasherize
- UnderscoresToDashes.each do |underscored, dasherized|
+ UnderscoresToDashes.each_key do |underscored|
assert_equal(underscored, ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.dasherize(underscored)))
end
end
diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb
index 62a9b61464..f46e96f636 100644
--- a/activesupport/test/notifications/instrumenter_test.rb
+++ b/activesupport/test/notifications/instrumenter_test.rb
@@ -34,6 +34,14 @@ module ActiveSupport
assert called
end
+ def test_instrument_yields_the_payload_for_further_modification
+ assert_equal 2, instrumenter.instrument("awesome") { |p| p[:result] = 1 + 1 }
+ assert_equal 1, notifier.finishes.size
+ name, _, payload = notifier.finishes.first
+ assert_equal "awesome", name
+ assert_equal Hash[:result => 2], payload
+ end
+
def test_start
instrumenter.start("foo", payload)
assert_equal [["foo", instrumenter.id, payload]], notifier.starts
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index bcb393c7bc..d63c59883a 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -157,7 +157,7 @@ module Notifications
assert_equal 2, instrument(:awesome) { 1 + 1 }
end
- def test_instrument_yields_the_paylod_for_further_modification
+ def test_instrument_yields_the_payload_for_further_modification
assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 }
assert_equal 1, @events.size
assert_equal :awesome, @events.first.name
diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb
index 19280ba74a..aca2951450 100644
--- a/activesupport/test/testing/constant_lookup_test.rb
+++ b/activesupport/test/testing/constant_lookup_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'dependencies_test_helpers'
class Foo; end
class Bar < Foo
@@ -10,6 +11,7 @@ module FooBar; end
class ConstantLookupTest < ActiveSupport::TestCase
include ActiveSupport::Testing::ConstantLookup
+ include DependenciesTestHelpers
def find_foo(name)
self.class.determine_constant_from_test_name(name) do |constant|
@@ -56,4 +58,12 @@ class ConstantLookupTest < ActiveSupport::TestCase
assert_nil find_module("DoesntExist::Nadda::Nope")
assert_nil find_module("DoesntExist::Nadda::Nope::NotHere")
end
+
+ def test_does_not_swallow_exception_on_no_method_error
+ assert_raises(NoMethodError) {
+ with_autoloading_fixtures {
+ self.class.determine_constant_from_test_name("RaisesNoMethodError")
+ }
+ }
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 9c3b5d0667..84c3154e53 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -232,6 +232,22 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Time.utc(2012, 5, 28, 7, 0, 0), twz.utc
end
+ def test_parse_doesnt_use_local_dst
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['UTC']
+ twz = zone.parse('2013-03-10 02:00:00')
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_parse_handles_dst_jump
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.parse('2013-03-10 02:00:00')
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get('America/New_York')
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index b5d8142458..ce91c443e1 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -17,7 +17,7 @@ class TransliterateTest < ActiveSupport::TestCase
# some reason or other are floating in the middle of all the letters.
string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*")
string.each_char do |char|
- assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string)
+ assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char)
end
end
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index f77d78d42c..2dfdde5c62 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -3,9 +3,12 @@ if RUBY_PLATFORM =~ /java/
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
+
class JDOMEngineTest < ActiveSupport::TestCase
include ActiveSupport
+ FILES_DIR = File.dirname(__FILE__) + '/../fixtures/xml'
+
def setup
@default_backend = XmlMini.backend
XmlMini.backend = 'JDOM'
@@ -30,10 +33,41 @@ if RUBY_PLATFORM =~ /java/
assert_equal 'image/png', file.content_type
end
+ def test_not_allowed_to_expand_entities_to_files
+ attack_xml = <<-EOT
+ <!DOCTYPE member [
+ <!ENTITY a SYSTEM "file://#{FILES_DIR}/jdom_include.txt">
+ ]>
+ <member>x&a;</member>
+ EOT
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+
+ def test_not_allowed_to_expand_parameter_entities_to_files
+ attack_xml = <<-EOT
+ <!DOCTYPE member [
+ <!ENTITY % b SYSTEM "file://#{FILES_DIR}/jdom_entities.txt">
+ %b;
+ ]>
+ <member>x&a;</member>
+ EOT
+ assert_raise Java::OrgXmlSax::SAXParseException do
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+ end
+
+
+ def test_not_allowed_to_load_external_doctypes
+ attack_xml = <<-EOT
+ <!DOCTYPE member SYSTEM "file://#{FILES_DIR}/jdom_doctype.dtd">
+ <member>x&a;</member>
+ EOT
+ assert_equal 'x', Hash.from_xml(attack_xml)["member"]
+ end
+
def test_exception_thrown_on_expansion_attack
- assert_raise NativeException do
+ assert_raise Java::OrgXmlSax::SAXParseException do
attack_xml = <<-EOT
- <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE member [
<!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
diff --git a/ci/travis.rb b/ci/travis.rb
index b03ac4fe35..9029c3f41c 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -109,7 +109,7 @@ end
# puts " #{`uname -a`}"
# puts " #{`ruby -v`}"
# puts " #{`mysql --version`}"
-# # puts " #{`pg_config --version`}"
+# puts " #{`pg_config --version`}"
# puts " SQLite3: #{`sqlite3 -version`}"
# `gem env`.each_line {|line| print " #{line}"}
# puts " Bundled gems:"
@@ -117,7 +117,7 @@ end
# puts " Local gems:"
# `gem list`.each_line {|line| print " #{line}"}
-failures = results.select { |key, value| value == false }
+failures = results.select { |key, value| !value }
if failures.empty?
puts
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
index c6750e1ae1..c0de53555d 100644
--- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png
+++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
Binary files differ
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index b355c7d91a..5b574cfc1d 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -4,26 +4,30 @@ gem 'rails', '4.0.0'
gem 'sqlite3'
-# Gems used only for assets and not required
-# in production environments by default.
-group :assets do
- gem 'sprockets-rails'
- gem 'sass-rails'
- gem 'coffee-rails'
+# Use SCSS for stylesheets
+gem 'sass-rails'
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- # gem 'therubyracer', platforms: :ruby
+# Use CoffeeScript for .js.coffee assets and views
+gem 'coffee-rails'
- gem 'uglifier', '>= 1.0.3'
-end
+# See https://github.com/sstephenson/execjs#readme for more supported runtimes
+# gem 'therubyracer', platforms: :ruby
+
+# Use Uglifier as compressor for JavaScript assets
+gem 'uglifier', '>= 1.0.3'
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
+group :doc do
+ # bundle exec rake doc:rails generates the API under doc/api.
+ gem 'sdoc'
+end
+
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 1.0.1'
+gem 'jbuilder', '~> 1.2'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb
index 526a782b5c..3d7604b659 100644
--- a/guides/code/getting_started/config/application.rb
+++ b/guides/code/getting_started/config/application.rb
@@ -2,8 +2,9 @@ require File.expand_path('../boot', __FILE__)
require 'rails/all'
-# Assets should be precompiled for production (so we don't need the gems loaded then)
-Bundler.require(*Rails.groups(assets: %w(development test)))
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env)
module Blog
class Application < Rails::Application
diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html
index ae7b8649ae..3d287b135d 100644
--- a/guides/code/getting_started/public/404.html
+++ b/guides/code/getting_started/public/404.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html
index 0b64eb4ae9..3b946bf4a4 100644
--- a/guides/code/getting_started/public/422.html
+++ b/guides/code/getting_started/public/422.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
+
+ body > p {
width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html
index 9641851e74..ccc4ad5656 100644
--- a/guides/code/getting_started/public/500.html
+++ b/guides/code/getting_started/public/500.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow: 0 3px 8px rgba(50,50,50,0.17);
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index cef82f3784..802455f612 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -31,20 +31,20 @@ Documentation
The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the [Ruby on Rails Guides](http://guides.rubyonrails.org/) project is the definitive source for information on major Rails components. In its first official release, the Guides page includes:
-* [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
-* [Rails Database Migrations](http://guides.rubyonrails.org/migrations.html)
-* [Active Record Associations](http://guides.rubyonrails.org/association_basics.html)
-* [Active Record Query Interface](http://guides.rubyonrails.org/active_record_querying.html)
-* [Layouts and Rendering in Rails](http://guides.rubyonrails.org/layouts_and_rendering.html)
-* [Action View Form Helpers](http://guides.rubyonrails.org/form_helpers.html)
-* [Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html)
-* [Action Controller Overview](http://guides.rubyonrails.org/action_controller_overview.html)
-* [Rails Caching](http://guides.rubyonrails.org/caching_with_rails.html)
-* [A Guide to Testing Rails Applications](http://guides.rubyonrails.org/testing.html)
-* [Securing Rails Applications](http://guides.rubyonrails.org/security.html)
-* [Debugging Rails Applications](http://guides.rubyonrails.org/debugging_rails_applications.html)
-* [Performance Testing Rails Applications](http://guides.rubyonrails.org/performance_testing.html)
-* [The Basics of Creating Rails Plugins](http://guides.rubyonrails.org/plugins.html)
+* [Getting Started with Rails](getting_started.html)
+* [Rails Database Migrations](migrations.html)
+* [Active Record Associations](association_basics.html)
+* [Active Record Query Interface](active_record_querying.html)
+* [Layouts and Rendering in Rails](layouts_and_rendering.html)
+* [Action View Form Helpers](form_helpers.html)
+* [Rails Routing from the Outside In](routing.html)
+* [Action Controller Overview](action_controller_overview.html)
+* [Rails Caching](caching_with_rails.html)
+* [A Guide to Testing Rails Applications](testing.html)
+* [Securing Rails Applications](security.html)
+* [Debugging Rails Applications](debugging_rails_applications.html)
+* [Performance Testing Rails Applications](performance_testing.html)
+* [The Basics of Creating Rails Plugins](plugins.html)
All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers.
@@ -236,7 +236,7 @@ This will enable recognition of (among others) these routes:
* Lead Contributor: [S. Brent Faulkner](http://www.unwwwired.net/)
* More information:
- * [Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html#nested-resources)
+ * [Rails Routing from the Outside In](routing.html#nested-resources)
* [What's New in Edge Rails: Shallow Routes](http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes)
### Method Arrays for Member or Collection Routes
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 403b22852b..5e99063da8 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -194,7 +194,7 @@ class PeopleController < ActionController::Base
# This will pass with flying colors as long as there's a person key
# in the parameters, otherwise it'll raise a
- # ActionController::MissingParameter exception, which will get
+ # ActionController::ParameterMissing exception, which will get
# caught by ActionController::Base and turned into that 400 Bad
# Request reply.
def update
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 31182e9aed..a0d962f9c4 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -564,31 +564,8 @@ config.action_mailer.smtp_settings = {
Mailer Testing
--------------
-By default Action Mailer does not send emails in the test environment. They are just added to the `ActionMailer::Base.deliveries` array.
-
-Testing mailers normally involves two things: One is that the mail was queued, and the other one that the email is correct. With that in mind, we could test our example mailer from above like so:
-
-```ruby
-class UserMailerTest < ActionMailer::TestCase
- def test_welcome_email
- user = users(:some_user_in_your_fixtures)
-
- # Send the email, then test that it got queued
- email = UserMailer.welcome_email(user).deliver
- assert !ActionMailer::Base.deliveries.empty?
-
- # Test the body of the sent email contains what we expect it to
- assert_equal [user.email], email.to
- assert_equal 'Welcome to My Awesome Site', email.subject
- assert_match "<h1>Welcome to example.com, #{user.name}</h1>", email.body.to_s
- assert_match 'you have joined to example.com community', email.body.to_s
- end
-end
-```
-
-In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect.
-
-NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in `ActionMailer::TestCase` tests. If you want to have a clean slate outside Action Mailer tests, you can reset it manually with: `ActionMailer::Base.deliveries.clear`
+You can find detailed instructions on how to test your mailers in our
+[testing guide](testing.html#testing-your-mailers).
Intercepting Emails
-------------------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index fcdb342a03..2589accadd 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -76,6 +76,7 @@ The methods are:
* `reorder`
* `reverse_order`
* `select`
+* `distinct`
* `uniq`
* `where`
@@ -576,10 +577,10 @@ ActiveModel::MissingAttributeError: missing attribute: <attribute>
Where `<attribute>` is the attribute you asked for. The `id` method will not raise the `ActiveRecord::MissingAttributeError`, so just be careful when working with associations because they need the `id` method to function properly.
-If you would like to only grab a single record per unique value in a certain field, you can use `uniq`:
+If you would like to only grab a single record per unique value in a certain field, you can use `distinct`:
```ruby
-Client.select(:name).uniq
+Client.select(:name).distinct
```
This would generate SQL like:
@@ -591,10 +592,10 @@ SELECT DISTINCT name FROM clients
You can also remove the uniqueness constraint:
```ruby
-query = Client.select(:name).uniq
+query = Client.select(:name).distinct
# => Returns unique names
-query.uniq(false)
+query.distinct(false)
# => Returns all names, even if there are duplicates
```
@@ -1434,7 +1435,7 @@ Client.where(active: true).pluck(:id)
# SELECT id FROM clients WHERE active = 1
# => [1, 2, 3]
-Client.uniq.pluck(:role)
+Client.distinct.pluck(:role)
# SELECT DISTINCT role FROM clients
# => ['admin', 'member', 'guest']
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index c6984b4a41..b6363bdfb1 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -53,6 +53,22 @@ The easiest and recommended way to get a development environment ready to hack i
In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html).
+
+Running an Application Against Your Local Branch
+------------------------------------------------
+
+The `--dev` flag of `rails new` generates an application that uses your local
+branch:
+
+```bash
+$ cd rails
+$ bundle exec rails new ~/my-test-app --dev
+```
+
+The application generated in `~/my-test-app` runs against your local branch
+and in particular sees any modifications upon server reboot.
+
+
Testing Active Record
---------------------
@@ -201,6 +217,17 @@ Now get busy and add or edit code. You’re on your branch now, so you can write
* Include tests that fail without your code, and pass with it.
* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
+It is not customary in Rails to run the full test suite before pushing
+changes. The railties test suite in particular takes a long time, and even
+more if the source code is mounted in `/vagrant` as happens in the recommended
+workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
+
+As a compromise, test what your code obviously affects, and if the change is
+not in railties run the whole test suite of the affected component. If all is
+green that's enough to propose your contribution. We have [Travis CI](https
+://travis-ci.org/) as a safety net for catching unexpected breakages
+elsewhere.
+
TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted.
### Follow the Coding Conventions
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 00939c4ff2..ac76f00832 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -676,7 +676,12 @@ There are now no strict dependencies on what the class is, only what the API for
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](http://guides.rubyonrails.org/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.
+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.
For locales, simply place the locale files in the `config/locales` directory, just like you would in an application.
@@ -918,7 +923,7 @@ initializer "blorgh.assets.precompile" do |app|
end
```
-For more information, read the [Asset Pipeline guide](http://guides.rubyonrails.org/asset_pipeline.html)
+For more information, read the [Asset Pipeline guide](asset_pipeline.html)
### Other gem dependencies
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4e935442cc..3f16ebcf1d 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -906,7 +906,21 @@ If the associated object is already saved, `fields_for` autogenerates a hidden i
### The Controller
-You do not need to write any specific controller code to use nested attributes. Create and update records as you would with a simple form.
+As usual you need to
+[whitelist the parameters](action_controller_overview.html#strong-parameters) in
+the controller before you pass them to the model:
+
+```ruby
+def create
+ @person = Person.new(person_params)
+ # ...
+end
+
+private
+def person_params
+ params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
+end
+```
### Removing Objects
@@ -937,6 +951,16 @@ If the hash of attributes for an object contains the key `_destroy` with a value
<% end %>
```
+Don't forget to update the whitelisted params in your controller to also include
+the `_destroy` field:
+
+```ruby
+def person_params
+ params.require(:person).
+ permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy])
+end
+```
+
### Preventing Empty Records
It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a `:reject_if` proc to `accepts_nested_attributes_for`. This proc will be called with each hash of attributes submitted by the form. If the proc returns `false` then Active Record will not build an associated object for that hash. The example below only tries to build an address if the `kind` attribute is set.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index faddda0c0a..3881bb1195 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -103,7 +103,7 @@ To verify that you have everything installed correctly, you should be able to ru
$ rails --version
```
-If it says something like "Rails 3.2.9", you are ready to continue.
+If it says something like "Rails 4.0.0", you are ready to continue.
### Creating the Blog Application
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 8ba5fa4601..412f2faaaa 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -266,7 +266,7 @@ def start
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
- puts "=> Call with -d to detach" unless options[:daemonize]
+ puts "=> Run `rails server -h` for more startup options"
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 7b206f8b4a..70061dc815 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,8 +1,7 @@
A Guide to Testing Rails Applications
=====================================
-This guide covers built-in mechanisms offered by Rails to test your
-application.
+This guide covers built-in mechanisms in Rails for testing your application.
After reading this guide, you will know:
@@ -38,11 +37,11 @@ Rails creates a `test` folder for you as soon as you create a Rails project usin
```bash
$ ls -F test
-
-fixtures/ functional/ integration/ test_helper.rb unit/
+controllers/ helpers/ mailers/ test_helper.rb
+fixtures/ integration/ models/
```
-The `unit` directory is meant to hold tests for your models, the `functional` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting.
+The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting.
Fixtures are a way of organizing test data; they reside in the `fixtures` folder.
@@ -140,10 +139,9 @@ The default test stub in `test/unit/post_test.rb` looks like this:
require 'test_helper'
class PostTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
```
@@ -224,34 +222,30 @@ TIP: You can see all these rake tasks and their descriptions by running `rake --
### Running Tests
-Running a test is as simple as invoking the file containing the test cases through Ruby:
+Running a test is as simple as invoking the file containing the test cases through `rails test` command.
```bash
-$ ruby -Itest test/unit/post_test.rb
-
-Loaded suite models/post_test
-Started
+$ rails test test/models/post_test.rb
.
-Finished in 0.023513 seconds.
-1 tests, 1 assertions, 0 failures, 0 errors
-```
+Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
-This will run all the test methods from the test case. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch.
+1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
+```
-You can also run a particular test method from the test case by using the `-n` switch with the `test method name`.
+You can also run a particular test method from the test case by running the test and using `-n` switch with the `test method name`.
```bash
-$ ruby -Itest test/unit/post_test.rb -n test_the_truth
-
-Loaded suite unit/post_test
-Started
+$ rails test test/models/post_test.rb -n test_the_truth
.
-Finished in 0.023513 seconds.
-1 tests, 1 assertions, 0 failures, 0 errors
+Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
+
+1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
+This will run all test methods from the test case. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch.
+
The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary.
To see how a test failure is reported, you can add a failing test to the `post_test.rb` test case.
@@ -266,17 +260,16 @@ end
Let us run this newly added test.
```bash
-$ ruby -Itest test/unit/post_test.rb -n test_should_not_save_post_without_title
-Loaded suite -e
-Started
+$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
F
-Finished in 0.102072 seconds.
+
+Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
1) Failure:
-test_should_not_save_post_without_title(PostTest) [/test/models/post_test.rb:6]:
-<false> is not true.
+test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
+Failed assertion, no message given.
-1 tests, 1 assertions, 1 failures, 0 errors
+1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
```
In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:
@@ -292,9 +285,8 @@ Running this test shows the friendlier assertion message:
```bash
1) Failure:
-test_should_not_save_post_without_title(PostTest) [/test/models/post_test.rb:6]:
-Saved the post without a title.
-<false> is not true.
+test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
+Saved the post without a title
```
Now to get this test to pass we can add a model level validation for the _title_ field.
@@ -308,13 +300,12 @@ end
Now the test should pass. Let us verify by running the test again:
```bash
-$ ruby -Itest test/unit/post_test.rb -n test_should_not_save_post_without_title
-Loaded suite unit/post_test
-Started
+$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
.
-Finished in 0.193608 seconds.
-1 tests, 1 assertions, 0 failures, 0 errors
+Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
+
+1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD).
@@ -334,18 +325,17 @@ end
Now you can see even more output in the console from running the tests:
```bash
-$ ruby -Itest test/unit/post_test.rb -n test_should_report_error
-Loaded suite -e
-Started
+$ rails test test/models/post_test.rb -n test_should_report_error
E
-Finished in 0.082603 seconds.
+
+Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
1) Error:
test_should_report_error(PostTest):
-NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x249d354>
- /test/models/post_test.rb:6:in `test_should_report_error'
+NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0>
+ test/models/post_test.rb:10:in `block in <class:PostTest>'
-1 tests, 0 assertions, 0 failures, 1 errors
+1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
```
Notice the 'E' in the output. It denotes a test with error.
@@ -511,6 +501,21 @@ You also have access to three instance variables in your functional tests:
* `@request` - The request
* `@response` - The response
+### Setting Headers and CGI variables
+
+Headers and cgi variables can be set directly on the `@request`
+instance variable:
+
+```ruby
+# setting a HTTP Header
+@request.headers["Accepts"] = "text/plain, text/html"
+get :index # simulate the request with custom header
+
+# setting a CGI variable
+@request.headers["HTTP_REFERER"] = "http://example.com/home"
+post :create # simulate the request with custom env variable
+```
+
### Testing Templates and Layouts
If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template`
@@ -642,12 +647,9 @@ Here's what a freshly-generated integration test looks like:
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
- fixtures :all
-
- # Replace this with your real tests.
- test "the truth" do
- assert true
- end
+ # test "the truth" do
+ # assert true
+ # end
end
```
@@ -755,23 +757,28 @@ 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 rake tasks to help in testing. The table below lists all rake tasks 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 |
+| ------------------------ | ----------- |
+| `rails test` | Runs all unit, functional and integration tests. You can also simply run `rails test` as Rails will run all the tests by default|
+| `rails test controllers` | Runs all the controller tests from `test/controllers`|
+| `rails test functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
+| `rails test helpers` | Runs all the helper tests from `test/helpers`|
+| `rails test integration` | Runs all the integration tests from `test/integration`|
+| `rails test mailers` | Runs all the mailer tests from `test/mailers`|
+| `rails test models` | Runs all the model tests from `test/models`|
+| `rails test units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
-| Tasks | Description |
-| ------------------------------- | ----------- |
-| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the 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:recent` | Tests recent changes|
-| `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git|
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
+There're also some test commands which you can initiate by running rake tasks:
+| Tasks | Description |
+| ------------------------ | ----------- |
+| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.|
+| `rake test:recent` | Tests recent changes|
+| `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git|
-Brief Note About `Test::Unit`
+Brief Note About `MiniTest`
-----------------------------
Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
@@ -920,19 +927,24 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
tests UserMailer
test "invite" do
- @expected.from = 'me@example.com'
- @expected.to = 'friend@example.com'
- @expected.subject = "You have been invited by #{@expected.from}"
- @expected.body = read_fixture('invite')
- @expected.date = Time.now
-
- assert_equal @expected.encoded, UserMailer.create_invite('me@example.com', 'friend@example.com', @expected.date).encoded
+ # Send the email, then test that it got queued
+ email = UserMailer.create_invite('me@example.com',
+ 'friend@example.com', Time.now).deliver
+ assert !ActionMailer::Base.deliveries.empty?
+
+ # Test the body of the sent email contains what we expect it to
+ assert_equal ['me@example.com'], email.from
+ assert_equal ['friend@example.com'], email.to
+ assert_equal 'You have been invited by me@example.com', email.subject
+ assert_equal read_fixture('invite').join, email.body.to_s
end
-
end
```
-In this test, `@expected` is an instance of `TMail::Mail` that you can use in your tests. It is defined in `ActionMailer::TestCase`. The test above uses `@expected` to construct an email, which it then asserts with email created by the custom mailer. The `invite` fixture is the body of the email and is used as the sample content to assert against. The helper `read_fixture` is used to read in the content from this file.
+In the test we send the email and store the returned object in the `email`
+variable. We then ensure that it was sent (the first assert), then, in the
+second batch of assertions, we ensure that the email does indeed contain what we
+expect. The helper `read_fixture` is used to read in the content from this file.
Here's the content of the `invite` fixture:
@@ -944,9 +956,17 @@ You have been invited.
Cheers!
```
-This is the right time to understand a little more about writing tests for your mailers. The line `ActionMailer::Base.delivery_method = :test` in `config/environments/test.rb` sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (`ActionMailer::Base.deliveries`).
+This is the right time to understand a little more about writing tests for your
+mailers. The line `ActionMailer::Base.delivery_method = :test` in
+`config/environments/test.rb` sets the delivery method to test mode so that
+email will not actually be delivered (useful to avoid spamming your users while
+testing) but instead it will be appended to an array
+(`ActionMailer::Base.deliveries`).
-This way, emails are not actually sent, simply constructed. The precise content of the email can then be checked against what is expected, as in the example above.
+NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in
+`ActionMailer::TestCase` tests. If you want to have a clean slate outside Action
+Mailer tests, you can reset it manually with:
+`ActionMailer::Base.deliveries.clear`
### Functional Testing
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 8961f08c0b..385aa3c51b 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -104,7 +104,17 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
### Action Pack
-* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so:
+* Rails 4.0 introduces `ActiveSupport::KeyGenerator` and uses this as a base from which to generate and verify signed cookies (among other things). Existing signed cookies generated with Rails 3.x will be transparently upgraded if you leave your existing `secret_token` in place and add the new `secret_key_base`.
+
+```ruby
+ # config/initializers/secret_token.rb
+ Myapp::Application.config.secret_token = 'existing secret token'
+ Myapp::Application.config.secret_key_base = 'new secret key base'
+```
+
+Please note that you should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. This is because cookies signed based on the new `secret_key_base` in Rails 4.x are not backwards compatible with Rails 3.x. You are free to leave your existing `secret_token` in place, not set the new `secret_key_base`, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete.
+
+* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore` which leverages the new `ActiveSupport::KeyGenerator`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so:
```ruby
# config/initializers/session_store.rb
@@ -129,6 +139,23 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
* Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
+* Rails 4.0 raises an `ArgumentError` if clashing named routes are defined. This can be triggered by explicitly defined named routes or by the `resources` method. Here are two examples that clash with routes named `example_path`:
+
+```ruby
+ get 'one' => 'test#example', as: :example
+ get 'two' => 'test#example', as: :example
+```
+
+```ruby
+ resources :examples
+ get 'clashing/:id' => 'test#example', as: :example
+```
+
+In the first case, you can simply avoid using the same name for multiple
+routes. In the second, you can use the `only` or `except` options provided by
+the `resources` method to restrict the routes created as detailed in the
+[Routing Guide](routing.html#restricting-the-routes-created).
+
* Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example:
```ruby
diff --git a/rails.gemspec b/rails.gemspec
index 8a5d042361..a223ea1413 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -16,9 +16,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.bindir = 'bin'
- s.executables = []
- s.files = ['README.rdoc'] + Dir['guides/**/*']
+ s.files = ['README.rdoc'] + Dir['guides/**/*']
s.add_dependency 'activesupport', version
s.add_dependency 'actionpack', version
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 4f7cb8254f..a69920252c 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,21 +1,91 @@
## Rails 4.0.0 (unreleased) ##
+* The application rake task `doc:rails` generates now an API like the
+ official one (except for the links to GitHub).
+
+ *Xavier Noria*
+
+* Allow vanilla apps to render CoffeeScript templates in production
+
+ Vanilla apps already render CoffeeScript templates in development and test
+ environments. With this change, the production behavior matches that of
+ the other environments.
+
+ Effectively, this meant moving coffee-rails (and the JavaScript runtime on
+ which it is dependent) from the :assets group to the top-level of the
+ generated Gemfile.
+
+ *Gabe Kopley*
+
+* `Rails.version` now returns an instance of `Gem::Version`
+
+ *Charlie Somerville*
+
+* Don't generate a scaffold.css when --no-assets is specified
+
+ *Kevin Glowacz*
+
+* Add support for generate scaffold password:digest
+
+ * adds password_digest attribute to the migration
+ * adds has_secure_password to the model
+ * adds password and password_confirmation password_fields to _form.html
+ * omits password from index.html and show.html
+ * adds password and password_confirmation to the controller
+ * adds unencrypted password and password_confirmation to the controller test
+ * adds encrypted password_digest to the fixture
+
+ *Sam Ruby*
+
+* Rails now generates a `test/test_helper.rb` file with `fixtures :all` commented out by default,
+ since we don't want to force loading all fixtures for user when a single test is run. However,
+ fixtures are still going to be loaded automatically for test suites.
+
+ To force all fixtures to be create in your database, use `rails test -f` to run your test.
+
+ *Prem Sichanugrist*
+
+* Add `rails test` command for running tests
+
+ To run all tests:
+
+ $ rails test
+
+ To run a test suite
+
+ $ rails test [models,helpers,units,controllers,mailers,...]
+
+ To run a selected test file(s):
+
+ $ rails test test/unit/foo_test.rb [test/unit/bar_test.rb ...]
+
+ To run a single test from a test file
+
+ $ rails test test/unit/foo_test.rb -n test_the_truth
+
+ For more information, see `rails test --help`.
+
+ This command will eventually replace `rake test:*` and `rake test` tasks.
+
+ *Prem Sichanugrist and Chris Toomey*
+
+* Improve service pages with new layout (404, etc). *Stanislav Sobolev*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
-* Change Service pages(404, etc). *Stanislav Sobolev*
* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments
and calculates number of functions.
*Hendy Tanata*
-* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. Consider
- using application template instead. See this guide for more detail:
+* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed.
+ Consider using application template instead. See this guide for more detail:
http://guides.rubyonrails.org/rails_application_templates.html
*Prem Sichanugrist*
-* fix rake db:* tasks to work with DATABASE_URL and without config/database.yml
+* Fix `rake db:*` tasks to work with `DATABASE_URL` and without `config/database.yml`.
*Terence Lee*
@@ -39,13 +109,13 @@
*Amparo Luna*
* Fixes database.yml when creating a new rails application with '.'
- Fix #8304
+ Fixes #8304.
*Jeremy W. Rowe*
* Restore Rails::Engine::Railties#engines with deprecation to ensure
compatibility with gems such as Thinking Sphinx
- Fix #8551
+ Fixes #8551.
*Tim Raymond*
@@ -119,7 +189,7 @@
* Environment name can be a start substring of the default environment names
(production, development, test). For example: tes, pro, prod, dev, devel.
- Fix #8628.
+ Fixes #8628.
*Mykola Kyryk*
@@ -129,7 +199,7 @@
* Quote column names in generates fixture files. This prevents
conflicts with reserved YAML keywords such as 'yes' and 'no'
- Fix #8612.
+ Fixes #8612.
*Yves Senn*
@@ -162,19 +232,19 @@
* Add `db` to list of folders included by `rake notes` and `rake notes:custom`. *Antonio Cangiano*
* Engines with a dummy app include the rake tasks of dependencies in the app namespace.
- Fix #8229
+ Fixes #8229.
*Yves Senn*
* Add `sqlserver.yml` template file to satisfy `-d sqlserver` being passed to `rails new`.
- Fix #6882
+ Fixes #6882.
*Robert Nesius*
* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres*
* Add dummy app Rake tasks when `--skip-test-unit` and `--dummy-path` is passed to the plugin generator.
- Fix #8121
+ Fixes #8121.
*Yves Senn*
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
new file mode 100644
index 0000000000..cadf0fb43e
--- /dev/null
+++ b/railties/RDOC_MAIN.rdoc
@@ -0,0 +1,73 @@
+== Welcome to \Rails
+
+\Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern.
+
+Understanding the MVC pattern is key to understanding \Rails. MVC divides your application
+into three layers, each with a specific responsibility.
+
+The View layer is composed of "templates" that are responsible for providing
+appropriate representations of your application's resources. Templates
+can come in a variety of formats, but most view templates are \HTML with embedded Ruby
+code (.erb files).
+
+The Model layer represents your domain model (such as Account, Product, Person, Post)
+and encapsulates the business logic that is specific to your application. In \Rails,
+database-backed model classes are derived from ActiveRecord::Base. Active Record allows
+you to present the data from database rows as objects and embellish these data objects
+with business logic methods. Although most \Rails models are backed by a database, models
+can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
+provided by the ActiveModel module. You can read more about Active Record in its
+{README}[link:/activerecord/README.rdoc].
+
+The Controller layer is responsible for handling incoming HTTP requests and providing a
+suitable response. Usually this means returning \HTML, but \Rails controllers can also
+generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models
+and render view templates in order to generate the appropriate HTTP response.
+
+In \Rails, the Controller and View layers are handled together by Action Pack.
+These two layers are bundled in a single package due to their heavy interdependence.
+This is unlike the relationship between Active Record and Action Pack, which are
+independent. Each of these packages can be used independently outside of \Rails. You
+can read more about Action Pack in its {README}[link:/actionpack/README.rdoc].
+
+== Getting Started
+
+1. Install \Rails at the command prompt if you haven't yet:
+
+ gem install rails
+
+2. At the command prompt, create a new \Rails application:
+
+ rails new myapp
+
+ where "myapp" is the application name.
+
+3. Change directory to +myapp+ and start the web server:
+
+ cd myapp; rails server
+
+ Run with <tt>--help</tt> or <tt>-h</tt> for options.
+
+4. Go to http://localhost:3000 and you'll see:
+
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+5. Follow the guidelines to start developing your application. You may find the following resources handy:
+
+* The README file created within your application.
+* {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html].
+* {Ruby on \Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book].
+* {Ruby on \Rails Guides}[http://guides.rubyonrails.org].
+* {The API Documentation}[http://api.rubyonrails.org].
+
+== Contributing
+
+We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails
+guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
+to proceed. {Join us}[http://contributors.rubyonrails.org]!
+
+
+== License
+
+Ruby on \Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT].
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index bb98bbe5bf..84f8b82ad5 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -82,10 +82,6 @@ module Rails
groups
end
- def version
- VERSION::STRING
- end
-
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 6c9c53fc69..19c2226619 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -1,5 +1,9 @@
require "rails"
+if defined?(Rake) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any?
+ ENV['RAILS_ENV'] ||= 'test'
+end
+
%w(
active_record
action_controller
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
new file mode 100644
index 0000000000..3e7223f075
--- /dev/null
+++ b/railties/lib/rails/api/task.rb
@@ -0,0 +1,147 @@
+require 'rdoc/task'
+
+module Rails
+ module API
+ class Task < RDoc::Task
+ RDOC_FILES = {
+ 'activesupport' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_support/**/*.rb
+ ),
+ :exclude => 'lib/active_support/vendor/*'
+ },
+
+ 'activerecord' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_record/**/*.rb
+ ),
+ :exclude => 'lib/active_record/vendor/*'
+ },
+
+ 'activemodel' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/active_model/**/*.rb
+ )
+ },
+
+ 'actionpack' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/abstract_controller/**/*.rb
+ lib/action_controller/**/*.rb
+ lib/action_dispatch/**/*.rb
+ lib/action_view/**/*.rb
+ ),
+ :exclude => 'lib/action_controller/vendor/*'
+ },
+
+ 'actionmailer' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ lib/action_mailer/**/*.rb
+ ),
+ :exclude => 'lib/action_mailer/vendor/*'
+ },
+
+ 'railties' => {
+ :include => %w(
+ README.rdoc
+ CHANGELOG.md
+ MIT-LICENSE
+ lib/**/*.rb
+ )
+ }
+ }
+
+ def initialize(name)
+ super
+
+ self.title = 'Ruby on Rails API'
+ self.rdoc_dir = api_dir
+
+ options << '-m' << api_main
+ options << '-e' << 'UTF-8'
+ options << '-f' << 'sdoc'
+ options << '-T' << 'rails'
+
+ configure_rdoc_files
+
+ before_running_rdoc do
+ setup_horo_variables
+ end
+ end
+
+ # Hack, ignore the desc calls performed by the original initializer.
+ def desc(description)
+ # no-op
+ end
+
+ def configure_rdoc_files
+ rdoc_files.include(api_main)
+
+ RDOC_FILES.each do |component, cfg|
+ cdr = component_root_dir(component)
+
+ Array(cfg[:include]).each do |pattern|
+ rdoc_files.include("#{cdr}/#{pattern}")
+ end
+
+ Array(cfg[:exclude]).each do |pattern|
+ rdoc_files.exclude("#{cdr}/#{pattern}")
+ end
+ end
+ end
+
+ def setup_horo_variables
+ ENV['HORO_PROJECT_NAME'] = 'Ruby on Rails'
+ ENV['HORO_PROJECT_VERSION'] = rails_version
+ end
+
+ def api_main
+ component_root_dir('railties') + '/RDOC_MAIN.rdoc'
+ end
+ end
+
+ class RepoTask < Task
+ def initialize(name)
+ super
+
+ options << '-g' # link to GitHub, SDoc flag
+ end
+
+ def component_root_dir(component)
+ component
+ end
+
+ def api_dir
+ 'doc/rdoc'
+ end
+
+ def rails_version
+ "master@#{`git rev-parse HEAD`[0, 7]}"
+ end
+ end
+
+ class AppTask < Task
+ def component_root_dir(gem_name)
+ $:.grep(%r{#{gem_name}[\w.-]*/lib\z}).first[0..-5]
+ end
+
+ def api_dir
+ 'doc/api'
+ end
+
+ def rails_version
+ Rails::VERSION::STRING
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 2417bdca21..563905e8b3 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -79,7 +79,7 @@ module Rails
@initialized = false
@reloaders = []
@routes_reloader = nil
- @env_config = nil
+ @app_env_config = nil
@ordered_railties = nil
@railties = nil
end
@@ -134,7 +134,7 @@ module Rails
# * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
#
def env_config
- @env_config ||= begin
+ @app_env_config ||= begin
if config.secret_key_base.nil?
ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base in config/initializers/secret_token.rb file. " +
"This should be used instead of the old deprecated config.secret_token in order to use the new EncryptedCookieStore. " +
@@ -149,6 +149,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.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index aacde52cfc..41d3722c18 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -5,6 +5,7 @@ aliases = {
"d" => "destroy",
"c" => "console",
"s" => "server",
+ "t" => "test",
"db" => "dbconsole",
"r" => "runner"
}
@@ -16,6 +17,7 @@ The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
+ test Running the test file (short-cut alias: "t")
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
new Create a new Rails application. "rails new my_app" creates a
@@ -78,6 +80,15 @@ when 'server'
server.start
end
+when 'test'
+ $LOAD_PATH.unshift("./test")
+ require 'rails/commands/test_runner'
+ options = Rails::TestRunner.parse_arguments(ARGV)
+ ENV['RAILS_ENV'] ||= options[:environment] || 'test'
+
+ require APP_PATH
+ Rails::TestRunner.start(ARGV, options)
+
when 'dbconsole'
require 'rails/commands/dbconsole'
Rails::DBConsole.start
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 2ff29418c6..d7b8ff4439 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -1,7 +1,7 @@
require 'rails/version'
if ['--version', '-v'].include?(ARGV.first)
- puts "Rails #{Rails::VERSION::STRING}"
+ puts "Rails #{Rails.version}"
exit(0)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index cdb29a8156..e3119ecf22 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -42,8 +42,12 @@ module Rails
set_environment
end
+ # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
def app
- @app ||= super.respond_to?(:to_app) ? super.to_app : super
+ @app ||= begin
+ app = super
+ app.respond_to?(:to_app) ? app.to_app : app
+ end
end
def opt_parser
@@ -58,7 +62,7 @@ module Rails
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
- puts "=> Call with -d to detach" unless options[:daemonize]
+ puts "=> Run `rails server -h` for more startup options"
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/commands/test_runner.rb b/railties/lib/rails/commands/test_runner.rb
new file mode 100644
index 0000000000..d8857bd183
--- /dev/null
+++ b/railties/lib/rails/commands/test_runner.rb
@@ -0,0 +1,146 @@
+require 'optparse'
+require 'minitest/unit'
+
+module Rails
+ # Handles all logic behind +rails test+ command.
+ class TestRunner
+ class << self
+ # Creates a new +TestRunner+ object with an array of test files to run
+ # based on the arguments. When no arguments are provided, it runs all test
+ # files. When a suite argument is provided, it runs only the test files in
+ # that suite. Otherwise, it runs the specified test file(s).
+ def start(files, options = {})
+ original_fixtures_options = options.delete(:fixtures)
+ options[:fixtures] = true
+
+ case files.first
+ when nil
+ new(Dir['test/**/*_test.rb'], options).run
+ when 'models'
+ new(Dir['test/models/**/*_test.rb'], options).run
+ when 'helpers'
+ new(Dir['test/helpers/**/*_test.rb'], options).run
+ when 'units'
+ new(Dir['test/{models,helpers,unit}/**/*_test.rb'], options).run
+ when 'controllers'
+ new(Dir['test/controllers/**/*_test.rb'], options).run
+ when 'mailers'
+ new(Dir['test/mailers/**/*_test.rb'], options).run
+ when 'functionals'
+ new(Dir['test/{controllers,mailers,functional}/**/*_test.rb'], options).run
+ when 'integration'
+ new(Dir['test/integration/**/*_test.rb'], options).run
+ else
+ options[:fixtures] = original_fixtures_options
+ new(files, options).run
+ end
+ end
+
+ # Parses arguments and sets them as option flags
+ def parse_arguments(arguments)
+ options = {}
+ orig_arguments = arguments.dup
+
+ OptionParser.new do |opts|
+ opts.banner = "Usage: rails test [path to test file(s) or test suite]"
+
+ opts.separator ""
+ opts.separator "Run a specific test file(s) or a test suite, under Rails'"
+ opts.separator "environment. If the file name(s) or suit name is omitted,"
+ opts.separator "Rails will run all tests."
+ opts.separator ""
+ opts.separator "Specific options:"
+
+ opts.on '-h', '--help', 'Display this help.' do
+ puts opts
+ exit
+ end
+
+ opts.on '-e', '--environment NAME', String, 'Specifies the environment to run this test under' do |e|
+ options[:environment] = e
+ end
+
+ opts.on '-f', '--fixtures', 'Load fixtures in test/fixtures/ before running the tests' do
+ options[:fixtures] = true
+ end
+
+ opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
+ options[:seed] = m.to_i
+ end
+
+ opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
+ options[:verbose] = true
+ end
+
+ opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |n|
+ options[:filter] = n
+ end
+
+ opts.separator ""
+ opts.separator "Support types of test suites:"
+ opts.separator "-------------------------------------------------------------"
+ opts.separator "* models (test/models/**/*)"
+ opts.separator "* helpers (test/helpers/**/*)"
+ opts.separator "* units (test/{models,helpers,unit}/**/*"
+ opts.separator "* controllers (test/controllers/**/*)"
+ opts.separator "* mailers (test/mailers/**/*)"
+ opts.separator "* functionals (test/{controllers,mailers,functional}/**/*)"
+ opts.separator "* integration (test/integration/**/*)"
+ opts.separator "-------------------------------------------------------------"
+
+ opts.parse! arguments
+ orig_arguments -= arguments
+ end
+ options
+ end
+ end
+
+ # Creates a new +TestRunner+ object with a list of test file paths.
+ def initialize(files, options)
+ @files = files
+
+ Rails.application.load_tasks
+ Rake::Task['db:test:load'].invoke
+
+ if options.delete(:fixtures)
+ if defined?(ActiveRecord::Base)
+ ActiveSupport::TestCase.send :include, ActiveRecord::TestFixtures
+ ActiveSupport::TestCase.fixture_path = "#{Rails.root}/test/fixtures/"
+ ActiveSupport::TestCase.fixtures :all
+ end
+ end
+
+ MiniTest::Unit.runner.options = options
+ MiniTest::Unit.output = SilentUntilSyncStream.new(MiniTest::Unit.output)
+ end
+
+ # Runs test files by evaluating each of them.
+ def run
+ @files.each { |filename| load(filename) }
+ end
+
+ # A null stream object which ignores everything until +sync+ has been set
+ # to true. This is only used to silence unnecessary output from MiniTest,
+ # as MiniTest calls +output.sync = true+ right before it outputs the first
+ # test result.
+ class SilentUntilSyncStream < File
+ # Creates a +SilentUntilSyncStream+ object by giving it a target stream
+ # object that will be assigned to +MiniTest::Unit.output+ after +sync+ is
+ # set to true.
+ def initialize(target_stream)
+ @target_stream = target_stream
+ super(File::NULL, 'w')
+ end
+
+ # Swaps +MiniTest::Unit.output+ to another stream when +sync+ is true.
+ def sync=(sync)
+ if sync
+ @target_stream.sync = true
+ MiniTest::Unit.output = @target_stream
+ end
+
+ super
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 4e703151ba..7971accef3 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,7 +1,7 @@
require 'digest/md5'
require 'securerandom'
require 'active_support/core_ext/string/strip'
-require 'rails/version' unless defined?(Rails::VERSION)
+require 'rails/version' unless defined?(Rails.version)
require 'rbconfig'
require 'open-uri'
require 'uri'
@@ -142,7 +142,7 @@ module Rails
else
<<-GEMFILE.strip_heredoc
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
- gem 'rails', '#{Rails::VERSION::STRING}'
+ gem 'rails', '#{Rails.version}'
GEMFILE
end
end
@@ -178,31 +178,46 @@ module Rails
return if options[:skip_sprockets]
gemfile = if options.dev? || options.edge?
- <<-GEMFILE
- # Gems used only for assets and not required
- # in production environments by default.
- group :assets do
- gem 'sprockets-rails', github: 'rails/sprockets-rails'
- gem 'sass-rails', github: 'rails/sass-rails'
- gem 'coffee-rails', github: 'rails/coffee-rails'
-
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- #{javascript_runtime_gemfile_entry}
- gem 'uglifier', '>= 1.0.3'
- end
+ <<-GEMFILE.gsub(/^ {12}/, '')
+ # Use edge version of sprockets-rails
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
+
+ # Use SCSS for stylesheets
+ gem 'sass-rails', github: 'rails/sass-rails'
+
+ # To use Uglifier as compressor for JavaScript assets
+ gem 'uglifier', '>= 1.3.0'
GEMFILE
else
- <<-GEMFILE
- # Gems used only for assets and not required
- # in production environments by default.
- group :assets do
- gem 'sass-rails', '~> 4.0.0.beta1'
- gem 'coffee-rails', '~> 4.0.0.beta1'
-
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- #{javascript_runtime_gemfile_entry}
- gem 'uglifier', '>= 1.0.3'
- end
+ <<-GEMFILE.gsub(/^ {12}/, '')
+ # Use SCSS for stylesheets
+ gem 'sass-rails', '~> 4.0.0.beta1'
+
+ # To use uglifier as compressor for JavaScript assets
+ gem 'uglifier', '>= 1.3.0'
+ GEMFILE
+ end
+
+ if options[:skip_javascript]
+ gemfile += <<-GEMFILE.gsub(/^ {12}/, '')
+ #{coffee_gemfile_entry}
+ #{javascript_runtime_gemfile_entry}
+ GEMFILE
+ end
+
+ gemfile.strip_heredoc.gsub(/^[ \t]*$/, '')
+ end
+
+ def coffee_gemfile_entry
+ gemfile = if options.dev? || options.edge?
+ <<-GEMFILE.gsub(/^ {12}/, '')
+ # Use CoffeeScript for .js.coffee assets and views
+ gem 'coffee-rails', github: 'rails/coffee-rails'
+ GEMFILE
+ else
+ <<-GEMFILE.gsub(/^ {12}/, '')
+ # Use CoffeeScript for .js.coffee assets and views
+ gem 'coffee-rails', '~> 4.0.0.beta1'
GEMFILE
end
@@ -210,9 +225,14 @@ module Rails
end
def javascript_gemfile_entry
+ args = {'jquery' => ", github: 'rails/jquery-rails'"}
+
unless options[:skip_javascript]
- <<-GEMFILE.strip_heredoc
- gem '#{options[:javascript]}-rails'
+ <<-GEMFILE.gsub(/^ {12}/, '').strip_heredoc
+ #{coffee_gemfile_entry}
+ #{javascript_runtime_gemfile_entry}
+
+ gem '#{options[:javascript]}-rails'#{args[options[:javascript]]}
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
@@ -221,11 +241,15 @@ module Rails
end
def javascript_runtime_gemfile_entry
- if defined?(JRUBY_VERSION)
- "gem 'therubyrhino'\n"
+ runtime = if defined?(JRUBY_VERSION)
+ "gem 'therubyrhino'"
else
- "# gem 'therubyracer', platforms: :ruby\n"
+ "# gem 'therubyracer', platforms: :ruby"
end
+ <<-GEMFILE.gsub(/^ {10}/, '')
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{runtime}
+ GEMFILE
end
def bundle_command(command)
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 32546936e3..85a1b01cc6 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -13,8 +13,17 @@
<% attributes.each do |attribute| -%>
<div class="field">
+<% if attribute.password_digest? -%>
+ <%%= f.label :password %><br />
+ <%%= f.password_field :password %>
+ </div>
+ <div>
+ <%%= f.label :password_confirmation %><br />
+ <%%= f.password_field :password_confirmation %>
+<% else -%>
<%%= f.label :<%= attribute.name %> %><br />
<%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
+<% end -%>
</div>
<% end -%>
<div class="actions">
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 90d8db1df5..d2fd99fdcb 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -3,7 +3,7 @@
<table>
<thead>
<tr>
-<% attributes.each do |attribute| -%>
+<% attributes.reject(&:password_digest?).each do |attribute| -%>
<th><%= attribute.human_name %></th>
<% end -%>
<th></th>
@@ -15,7 +15,7 @@
<tbody>
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
<tr>
-<% attributes.each do |attribute| -%>
+<% attributes.reject(&:password_digest?).each do |attribute| -%>
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
<% end -%>
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index daae72270f..5e634153be 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -1,6 +1,6 @@
<p id="notice"><%%= notice %></p>
-<% attributes.each do |attribute| -%>
+<% attributes.reject(&:password_digest?).each do |attribute| -%>
<p>
<strong><%= attribute.human_name %>:</strong>
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 4ae8756ed0..5e2784c4b0 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -130,6 +130,10 @@ module Rails
@has_uniq_index
end
+ def password_digest?
+ name == 'password' && type == :digest
+ end
+
def inject_options
"".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } }
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 9965db98de..8b4f52bb3b 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -163,6 +163,7 @@ module Rails
def attributes_names
@attributes_names ||= attributes.each_with_object([]) do |a, names|
names << a.column_name
+ names << 'password_confirmation' if a.password_digest?
names << "#{a.name}_type" if a.polymorphic?
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b5db3d2187..b3883a94fc 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -9,6 +9,11 @@ source 'https://rubygems.org'
<%= assets_gemfile_entry %>
<%= javascript_gemfile_entry -%>
+group :doc do
+ # bundle exec rake doc:rails generates the API under doc/api.
+ gem 'sdoc'
+end
+
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.0.1'
@@ -23,5 +28,5 @@ gem 'jbuilder', '~> 1.0.1'
<% unless defined?(JRUBY_VERSION) -%>
# To use debugger
-# gem 'debugger'
+# gem 'debugger', group: [:development, :test]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index fcfbc6b07a..5bc2a619e8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run <%= app_const %>
+run Rails.application
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 daf399a538..ceb2bdf371 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -11,8 +11,9 @@ require "action_mailer/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
-# Assets should be precompiled for production (so we don't need the gems loaded then)
-Bundler.require(*Rails.groups(assets: %w(development test)))
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env)
module <%= app_const_base %>
class Application < Rails::Application
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 d0e62d09cc..8b64881dbc 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
@@ -26,6 +26,8 @@
<%- unless options.skip_sprockets? -%>
# Debug mode disables concatenation and preprocessing of assets.
+ # This option may cause significant delays in view rendering with a large
+ # number of complex assets.
config.assets.debug = true
<%- end -%>
end
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/initializers/secret_token.rb.tt
index e5caab3672..efccf72d3d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Your secret key for verifying the integrity of signed cookies.
+# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 0ee82d3722..a0daa0c156 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html
index f1f32b83ae..fbb4b84d72 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
+
+ body > p {
width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index 9417de0cc0..e9052d35bf 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -2,16 +2,15 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style>
- body
- {
- background-color: #efefef;
+ <style>
+ body {
+ background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
- font-family: arial,sans-serif;
+ font-family: arial, sans-serif;
}
- div.dialog
- {
+
+ div.dialog {
width: 25em;
margin: 4em auto 0 auto;
border: 1px solid #CCC;
@@ -24,17 +23,18 @@
background-color: white;
padding: 7px 4em 0 4em;
}
- h1{
+
+ h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- body>p
- {
- width: 33em;
+
+ body > p {
+ width: 33em;
margin: 0 auto 1em;
padding: 1em 0;
- background-color: #f7f7f7;
+ background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-bottom-color: #999;
@@ -42,7 +42,7 @@
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
- box-shadow:0 3px 8px rgba(50,50,50,0.17);
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
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 9afda2d0df..ca40914d3b 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -1,4 +1,4 @@
-ENV["RAILS_ENV"] = "test"
+ENV["RAILS_ENV"] ||= "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
@@ -6,11 +6,12 @@ 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.
+ # Uncomment the `fixtures :all` line below to 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
# -- they do not yet inherit this setting
- fixtures :all
+ # fixtures :all
<% end -%>
# Add more helper methods to be used by all tests here...
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index f7c12e67dd..6373ca711e 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
s.test_files = Dir["test/**/*"]
<% end -%>
- <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
+ <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails.version %>"
<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database %>"
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index 3f2b78f2fd..de00ab057d 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,7 +1,7 @@
source "https://rubygems.org"
<% if options[:skip_gemspec] -%>
-<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
+<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails.version %>"
<% else -%>
# Declare your gem's dependencies in <%= name %>.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 36589d65e2..2a0522e81c 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -10,6 +10,7 @@ module Rails
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
def handle_skip
+ @options = @options.merge(stylesheets: false) unless options[:assets]
@options = @options.merge(stylesheet_engine: false) unless options[:stylesheets]
end
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index c9d505c84a..90a92e6982 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -1,22 +1,20 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
-
<% unless attributes.empty? -%>
-one:
+<% %w(one two).each do |name| %>
+<%= name %>:
<% attributes.each do |attribute| -%>
+ <%- if attribute.password_digest? -%>
+ password_digest: <%%= BCrypt::Password.create('secret') %>
+ <%- else -%>
<%= yaml_key_value(attribute.column_name, attribute.default) %>
- <%- if attribute.polymorphic? -%>
- <%= yaml_key_value("#{attribute.name}_type", attribute.human_name) %>
<%- end -%>
-<% end -%>
-
-two:
-<% attributes.each do |attribute| -%>
- <%= yaml_key_value(attribute.column_name, attribute.default) %>
<%- if attribute.polymorphic? -%>
<%= yaml_key_value("#{attribute.name}_type", attribute.human_name) %>
<%- end -%>
<% end -%>
+<% end -%>
<% else -%>
+
# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 8f3ecaadea..2e1f55f2a6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -21,7 +21,11 @@ module TestUnit # :nodoc:
return if attributes_names.empty?
attributes_names.map do |name|
- "#{name}: @#{singular_table_name}.#{name}"
+ if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?)
+ "#{name}: 'secret'"
+ else
+ "#{name}: @#{singular_table_name}.#{name}"
+ end
end.sort.join(', ')
end
end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 592e74726e..f06ce659c5 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -29,7 +29,7 @@ module Rails
def framework_version(framework)
if Object.const_defined?(framework.classify)
require "#{framework}/version"
- "#{framework.classify}::VERSION::STRING".constantize
+ framework.classify.constantize.version.to_s
end
end
@@ -75,7 +75,7 @@ module Rails
# The Rails version.
property 'Rails version' do
- Rails::VERSION::STRING
+ Rails.version.to_s
end
property 'JavaScript Runtime' do
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 0057b0f887..1c3426028d 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -1,4 +1,5 @@
require 'rdoc/task'
+require 'rails/api/task'
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
@@ -52,52 +53,7 @@ namespace :doc do
Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
# desc 'Generate documentation for the Rails framework.'
- RDocTaskWithoutDescriptions.new("rails") { |rdoc|
- rdoc.rdoc_dir = 'doc/api'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
- rdoc.title = "Rails Framework Documentation"
- rdoc.options << '--line-numbers'
-
- gem_path('rails') do |rails|
- rdoc.options << '-m' << "#{rails}/README.rdoc"
- end
-
- gem_path('actionmailer') do |actionmailer|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_mailer/base.rb).each do |file|
- rdoc.rdoc_files.include("#{actionmailer}/#{file}")
- end
- end
-
- gem_path('actionpack') do |actionpack|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{actionpack}/#{file}")
- end
- end
-
- gem_path('activemodel') do |activemodel|
- %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/active_model/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activemodel}/#{file}")
- end
- end
-
- gem_path('activerecord') do |activerecord|
- %w(README.rdoc CHANGELOG.md lib/active_record/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activerecord}/#{file}")
- end
- end
-
- gem_path('activesupport') do |activesupport|
- %w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file|
- rdoc.rdoc_files.include("#{activesupport}/#{file}")
- end
- end
-
- gem_path('railties') do |railties|
- %w(README.rdoc CHANGELOG.md lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
- rdoc.rdoc_files.include("#{railties}/#{file}")
- end
- end
- }
+ Rails::API::AppTask.new('rails')
# desc "Generate Rails Guides"
task :guides do
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 44485d9b14..3c247f32c0 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,6 +1,7 @@
require 'rbconfig'
require 'rake/testtask'
require 'rails/test_unit/sub_test_task'
+require 'active_support/deprecation'
TEST_CHANGES_SINCE = Time.now - 600
@@ -47,7 +48,11 @@ task default: :test
desc 'Runs test:units, test:functionals, test:integration together'
task :test do
- Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
+ if ENV['TEST']
+ exec "bundle exec rails test #{ENV['TEST'].inspect}"
+ else
+ exec 'bundle exec rails test'
+ end
end
namespace :test do
@@ -56,19 +61,8 @@ namespace :test do
end
task :run do
- errors = %w(test:units test:functionals test:integration).collect do |task|
- begin
- Rake::Task[task].invoke
- nil
- rescue => e
- { task: task, exception: e }
- end
- end.compact
-
- if errors.any?
- puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n")
- abort
- end
+ ActiveSupport::Deprecation.warn "`rake test:run` is deprecated. Please use `rails test`."
+ exec 'bundle exec rails test'
end
# Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
@@ -83,7 +77,12 @@ namespace :test do
task :db => %w[db:test:prepare test:all]
end
- Rake::TestTask.new(recent: "test:prepare") do |t|
+ # Display deprecation message
+ task :deprecated do
+ ActiveSupport::Deprecation.warn "`rake #{ARGV.first}` is deprecated with no replacement."
+ end
+
+ Rake::TestTask.new(recent: ["test:deprecated", "test:prepare"]) do |t|
since = TEST_CHANGES_SINCE
touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } +
recent_tests('app/models/**/*.rb', 'test/models', since) +
@@ -94,9 +93,9 @@ namespace :test do
t.libs << 'test'
t.test_files = touched.uniq
end
- Rake::Task['test:recent'].comment = "Test recent changes"
+ Rake::Task['test:recent'].comment = "Deprecated; Test recent changes"
- Rake::TestTask.new(uncommitted: "test:prepare") do |t|
+ Rake::TestTask.new(uncommitted: ["test:deprecated", "test:prepare"]) do |t|
def t.file_list
if File.directory?(".svn")
changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] }
@@ -118,44 +117,20 @@ namespace :test do
t.libs << 'test'
end
- Rake::Task['test:uncommitted'].comment = "Test changes since last checkin (only Subversion and Git)"
-
- Rake::TestTask.new(single: "test:prepare") do |t|
- t.libs << "test"
- end
-
- Rails::SubTestTask.new(models: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/models/**/*_test.rb'
- end
-
- Rails::SubTestTask.new(helpers: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/helpers/**/*_test.rb'
- end
+ Rake::Task['test:uncommitted'].comment = "Deprecated; Test changes since last checkin (only Subversion and Git)"
- Rails::SubTestTask.new(units: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/{models,helpers,unit}/**/*_test.rb'
+ desc "Deprecated; Please use `rails test \"#{ENV['TEST']}\"`"
+ task :single do
+ ActiveSupport::Deprecation.warn "`rake test:single` is deprecated. Please use `rails test \"#{ENV['TEST']}\"`."
+ exec "bundle exec rails test #{test_suit_name}"
end
- Rails::SubTestTask.new(controllers: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/controllers/**/*_test.rb'
- end
+ [:models, :helpers, :units, :controllers, :functionals, :integration].each do |test_suit_name|
+ desc "Deprecated; Please use `rails test #{test_suit_name}`"
+ task test_suit_name do
+ ActiveSupport::Deprecation.warn "`rake test:#{test_suit_name}` is deprecated. Please use `rails test #{test_suit_name}`."
- Rails::SubTestTask.new(mailers: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/mailers/**/*_test.rb'
- end
-
- Rails::SubTestTask.new(functionals: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb'
- end
-
- Rails::SubTestTask.new(integration: "test:prepare") do |t|
- t.libs << "test"
- t.pattern = 'test/integration/**/*_test.rb'
+ exec "bundle exec rails test #{test_suit_name}"
+ end
end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 87fc7690ac..d1d02e086d 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,10 +1,11 @@
module Rails
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded Rails as a Gem::Version
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = Rails.version.segments
+ STRING = Rails.version.to_s
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index a55bf012da..276cce3a77 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'RDOC_MAIN.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
s.bindir = 'bin'
@@ -28,5 +28,4 @@ Gem::Specification.new do |s|
s.add_dependency 'rake', '>= 0.8.7'
s.add_dependency 'thor', '>= 0.17.0', '< 2.0'
- s.add_dependency 'rdoc', '~> 3.4'
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 1eddfac664..b3b40448c0 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -48,7 +48,7 @@ module ApplicationTests
assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
- test "assets aren't concatened when compile is true is on and debug_assets params is true" do
+ test "assets aren't concatenated when compile is true is on and debug_assets params is true" do
add_to_env_config "production", "config.assets.compile = true"
ENV["RAILS_ENV"] = "production"
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 638df8ca23..e5503b1eff 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -86,8 +86,8 @@ module ApplicationTests
def test_precompile_does_not_hit_the_database
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
- app_file "app/controllers/user_controller.rb", <<-eoruby
- class UserController < ApplicationController; end
+ app_file "app/controllers/users_controller.rb", <<-eoruby
+ class UsersController < ApplicationController; end
eoruby
app_file "app/models/user.rb", <<-eoruby
class User < ActiveRecord::Base; end
@@ -221,7 +221,7 @@ module ApplicationTests
assert !defined?(Uglifier)
end
- test "precompile properly refers files referenced with asset_path and and run in the provided RAILS_ENV" do
+ test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
# digest is default in false, we must enable it for test environment
add_to_env_config "test", "config.assets.digest = true"
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7b45623f6c..1acf03f35a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -599,7 +599,7 @@ module ApplicationTests
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
- test "config.action_controller.action_on_unpermitted_parameters is :log by defaul on test" do
+ test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do
ENV["RAILS_ENV"] = "test"
require "#{app_path}/config/environment"
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index f4450c8a3c..80700a1d64 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -106,13 +106,13 @@ class FullStackConsoleTest < ActiveSupport::TestCase
teardown_app
end
- def assert_output(expected, timeout = 5)
+ def assert_output(expected, timeout = 1)
timeout = Time.now + timeout
output = ""
until output.include?(expected) || Time.now > timeout
if IO.select([@master], [], [], 0.1)
- output << @master.readpartial(100)
+ output << @master.read(1)
end
end
@@ -126,12 +126,6 @@ class FullStackConsoleTest < ActiveSupport::TestCase
assert_output "> "
end
- def kill(pid)
- Process.kill('QUIT', pid)
- Process.wait(pid)
- rescue Errno::ESRCH
- end
-
def spawn_console
pid = Process.spawn(
"#{app_path}/bin/rails console --sandbox",
@@ -148,15 +142,13 @@ class FullStackConsoleTest < ActiveSupport::TestCase
write_prompt "Post.count", "=> 0"
write_prompt "Post.create"
write_prompt "Post.count", "=> 1"
-
- kill pid
+ @master.puts "quit"
pid = spawn_console
write_prompt "Post.count", "=> 0"
write_prompt "Post.transaction { Post.create; raise }"
write_prompt "Post.count", "=> 0"
- ensure
- kill pid if pid
+ @master.puts "quit"
end
end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 489b7ddb92..17d0b10b70 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -45,7 +45,7 @@ module ApplicationTests
end
# Load paths
- test "no config locales dir present should return empty load path" do
+ test "no config locales directory present should return empty load path" do
FileUtils.rm_rf "#{app_path}/config/locales"
load_app
assert_equal [], Rails.application.config.i18n.load_path
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index 31811e7f92..9b18c329ec 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -23,7 +23,7 @@ module ApplicationTests
assert $:.include?("#{app_path}/app/models")
end
- test "initializing an application allows to load code on lib path inside application class definitation" do
+ test "initializing an application allows to load code on lib path inside application class definition" do
app_file "lib/foo.rb", <<-RUBY
module Foo; end
RUBY
diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb
index 18af7abafc..bbb7627be9 100644
--- a/railties/test/application/middleware/cookies_test.rb
+++ b/railties/test/application/middleware/cookies_test.rb
@@ -33,7 +33,7 @@ module ApplicationTests
assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
end
- test 'always_write_cookie can be overrided' do
+ test 'always_write_cookie can be overridden' do
add_to_config <<-RUBY
config.action_dispatch.always_write_cookie = false
RUBY
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 09f2ad1209..a9e0e1bcb7 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -91,19 +91,9 @@ module ApplicationTests
raise 'models'
RUBY
- app_file "test/controllers/one_controller_test.rb", <<-RUBY
- raise 'controllers'
- RUBY
-
- app_file "test/integration/one_integration_test.rb", <<-RUBY
- raise 'integration'
- RUBY
-
silence_stderr do
output = Dir.chdir(app_path) { `rake test 2>&1` }
assert_match 'models', output
- assert_match 'controllers', output
- assert_match 'integration', output
end
end
@@ -135,6 +125,19 @@ module ApplicationTests
end
end
+ def test_rake_test_deprecation_messages
+ Dir.chdir(app_path){ `rails generate scaffold user name:string` }
+ Dir.chdir(app_path){ `rake db:migrate` }
+
+ %w(run recent uncommitted models helpers units controllers functionals integration).each do |test_suit_name|
+ output = Dir.chdir(app_path) { `rake test:#{test_suit_name} 2>&1` }
+ assert_match /DEPRECATION WARNING: `rake test:#{test_suit_name}` is deprecated/, output
+ end
+
+ assert_match /DEPRECATION WARNING: `rake test:single` is deprecated/,
+ Dir.chdir(app_path) { `rake test:single TEST=test/models/user_test.rb 2>&1` }
+ end
+
def test_rake_routes_calls_the_route_inspector
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
new file mode 100644
index 0000000000..56ca3bc1a9
--- /dev/null
+++ b/railties/test/application/test_runner_test.rb
@@ -0,0 +1,312 @@
+require 'isolation/abstract_unit'
+require 'active_support/core_ext/string/strip'
+
+module ApplicationTests
+ class TestRunnerTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ ENV['RAILS_ENV'] = nil
+ create_schema
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def test_should_not_display_heading
+ create_test_file
+ run_test_command.tap do |output|
+ assert_no_match "Run options:", output
+ assert_no_match "Running tests:", output
+ end
+ end
+
+ def test_run_in_test_environment
+ app_file 'test/unit/env_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class EnvTest < ActiveSupport::TestCase
+ def test_env
+ puts "Current Environment: \#{Rails.env}"
+ end
+ end
+ RUBY
+
+ assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb')
+ end
+
+ def test_run_shortcut
+ create_test_file :models, 'foo'
+ output = Dir.chdir(app_path) { `bundle exec rails t test/models/foo_test.rb` }
+ assert_match "1 tests, 1 assertions, 0 failures", output
+ end
+
+ def test_run_single_file
+ create_test_file :models, 'foo'
+ assert_match "1 tests, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb")
+ end
+
+ def test_run_multiple_files
+ create_test_file :models, 'foo'
+ create_test_file :models, 'bar'
+ assert_match "2 tests, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb")
+ end
+
+ def test_run_file_with_syntax_error
+ app_file 'test/models/error_test.rb', <<-RUBY
+ require 'test_helper'
+ def; end
+ RUBY
+
+ error_stream = Tempfile.new('error')
+ redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') }
+ assert_match "SyntaxError", error_stream.read
+ end
+
+ def test_invoke_rake_db_test_load
+ app_file "lib/tasks/test.rake", <<-RUBY
+ task 'db:test:load' do
+ puts "Hello World"
+ end
+ RUBY
+ create_test_file
+ assert_match "Hello World", run_test_command
+ end
+
+ def test_run_models
+ create_test_file :models, 'foo'
+ create_test_file :models, 'bar'
+ create_test_file :controllers, 'foobar_controller'
+ run_test_command("models").tap do |output|
+ assert_match "FooTest", output
+ assert_match "BarTest", output
+ assert_match "2 tests, 2 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_helpers
+ create_test_file :helpers, 'foo_helper'
+ create_test_file :helpers, 'bar_helper'
+ create_test_file :controllers, 'foobar_controller'
+ run_test_command('helpers').tap do |output|
+ assert_match "FooHelperTest", output
+ assert_match "BarHelperTest", output
+ assert_match "2 tests, 2 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_units
+ create_test_file :models, 'foo'
+ create_test_file :helpers, 'bar_helper'
+ create_test_file :unit, 'baz_unit'
+ create_test_file :controllers, 'foobar_controller'
+ run_test_command('units').tap do |output|
+ assert_match "FooTest", output
+ assert_match "BarHelperTest", output
+ assert_match "BazUnitTest", output
+ assert_match "3 tests, 3 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_controllers
+ create_test_file :controllers, 'foo_controller'
+ create_test_file :controllers, 'bar_controller'
+ create_test_file :models, 'foo'
+ run_test_command('controllers').tap do |output|
+ assert_match "FooControllerTest", output
+ assert_match "BarControllerTest", output
+ assert_match "2 tests, 2 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_mailers
+ create_test_file :mailers, 'foo_mailer'
+ create_test_file :mailers, 'bar_mailer'
+ create_test_file :models, 'foo'
+ run_test_command('mailers').tap do |output|
+ assert_match "FooMailerTest", output
+ assert_match "BarMailerTest", output
+ assert_match "2 tests, 2 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_functionals
+ create_test_file :mailers, 'foo_mailer'
+ create_test_file :controllers, 'bar_controller'
+ create_test_file :functional, 'baz_functional'
+ create_test_file :models, 'foo'
+ run_test_command('functionals').tap do |output|
+ assert_match "FooMailerTest", output
+ assert_match "BarControllerTest", output
+ assert_match "BazFunctionalTest", output
+ assert_match "3 tests, 3 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_integration
+ create_test_file :integration, 'foo_integration'
+ create_test_file :models, 'foo'
+ run_test_command('integration').tap do |output|
+ assert_match "FooIntegration", output
+ assert_match "1 tests, 1 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_all_suites
+ suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration]
+ suites.each { |suite| create_test_file suite, "foo_#{suite}" }
+ run_test_command('') .tap do |output|
+ suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output }
+ assert_match "7 tests, 7 assertions, 0 failures", output
+ end
+ end
+
+ def test_run_named_test
+ app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class Chu2KoiTest < ActiveSupport::TestCase
+ def test_rikka
+ puts 'Rikka'
+ end
+
+ def test_sanae
+ puts 'Sanae'
+ end
+ end
+ RUBY
+
+ run_test_command('test/unit/chu_2_koi_test.rb -n test_rikka').tap do |output|
+ assert_match "Rikka", output
+ assert_no_match "Sanae", output
+ end
+ end
+
+ def test_not_load_fixtures_when_running_single_test
+ create_model_with_fixture
+ create_fixture_test :models, 'user'
+ assert_match "0 users", run_test_command('test/models/user_test.rb')
+ assert_match "3 users", run_test_command('test/models/user_test.rb -f')
+ end
+
+ def test_load_fixtures_when_running_test_suites
+ create_model_with_fixture
+ suites = [:models, :helpers, [:units, :unit], :controllers, :mailers,
+ [:functionals, :functional], :integration]
+
+ suites.each do |suite, directory|
+ directory ||= suite
+ create_fixture_test directory
+ assert_match "3 users", run_test_command(suite)
+ Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" }
+ end
+ end
+
+ def test_run_different_environment_using_env_var
+ app_file 'test/unit/env_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class EnvTest < ActiveSupport::TestCase
+ def test_env
+ puts Rails.env
+ end
+ end
+ RUBY
+
+ ENV['RAILS_ENV'] = 'development'
+ assert_match "development", run_test_command('test/unit/env_test.rb')
+ end
+
+ def test_run_different_environment_using_e_tag
+ app_file 'test/unit/env_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class EnvTest < ActiveSupport::TestCase
+ def test_env
+ puts Rails.env
+ end
+ end
+ RUBY
+
+ assert_match "development", run_test_command('-e development test/unit/env_test.rb')
+ end
+
+ def test_generated_scaffold_works_with_rails_test
+ create_scaffold
+ assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
+ end
+
+ private
+ def run_test_command(arguments = 'test/unit/test_test.rb')
+ Dir.chdir(app_path) { `bundle exec rails test #{arguments}` }
+ end
+
+ def create_model_with_fixture
+ script 'generate model user name:string'
+
+ app_file 'test/fixtures/users.yml', <<-YAML.strip_heredoc
+ vampire:
+ id: 1
+ name: Koyomi Araragi
+ crab:
+ id: 2
+ name: Senjougahara Hitagi
+ cat:
+ id: 3
+ name: Tsubasa Hanekawa
+ YAML
+
+ run_migration
+ end
+
+ def create_fixture_test(path = :unit, name = 'test')
+ app_file "test/#{path}/#{name}_test.rb", <<-RUBY
+ require 'test_helper'
+
+ class #{name.camelize}Test < ActiveSupport::TestCase
+ def test_fixture
+ puts "\#{User.count} users (\#{__FILE__})"
+ end
+ end
+ RUBY
+ end
+
+ def create_schema
+ app_file 'db/schema.rb', ''
+ end
+
+ def redirect_stderr(target_stream)
+ previous_stderr = STDERR.dup
+ $stderr.reopen(target_stream)
+ yield
+ target_stream.rewind
+ ensure
+ $stderr = previous_stderr
+ end
+
+ def create_test_file(path = :unit, name = 'test')
+ app_file "test/#{path}/#{name}_test.rb", <<-RUBY
+ require 'test_helper'
+
+ class #{name.camelize}Test < ActiveSupport::TestCase
+ def test_truth
+ puts "#{name.camelize}Test"
+ assert true
+ end
+ end
+ RUBY
+ end
+
+ def create_scaffold
+ script 'generate scaffold user name:string'
+ Dir.chdir(app_path) { File.exist?('app/models/user.rb') }
+ run_migration
+ end
+
+ def run_migration
+ Dir.chdir(app_path) { `bundle exec rake db:migrate` }
+ end
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 0697035871..b813a7f6bb 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -232,8 +232,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
assert_file "Gemfile" do |content|
assert_no_match(/sass-rails/, content)
- assert_no_match(/coffee-rails/, content)
assert_no_match(/uglifier/, content)
+ assert_match(/coffee-rails/, content)
end
assert_file "config/environments/development.rb" do |content|
assert_no_match(/config\.assets\.debug = true/, content)
@@ -276,7 +276,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
- assert_gem "jquery-rails"
+ assert_file "Gemfile", /^gem 'jquery-rails'/
end
def test_other_javascript_libraries
@@ -293,6 +293,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/assets/javascripts/application.js" do |contents|
assert_no_match %r{^//=\s+require\s}, contents
end
+ assert_file "Gemfile" do |content|
+ assert_match(/coffee-rails/, content)
+ end
end
def test_inclusion_of_debugger
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 34441ef679..48425cbf81 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -292,7 +292,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
assert_no_match(/# gem "jquery-rails"/, contents)
end
@@ -303,7 +303,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 357f472a3f..25f299118c 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -241,7 +241,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
def test_scaffold_generator_no_assets
run_generator [ "posts", "--no-assets" ]
- assert_file "app/assets/stylesheets/scaffold.css"
+ assert_no_file "app/assets/stylesheets/scaffold.css"
assert_no_file "app/assets/javascripts/posts.js"
assert_no_file "app/assets/stylesheets/posts.css"
end
@@ -271,4 +271,45 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_scaffold_generator_password_digest
+ run_generator ["user", "name", "password:digest"]
+
+ assert_file "app/models/user.rb", /has_secure_password/
+
+ assert_migration "db/migrate/create_users.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/t\.string :name/, up)
+ assert_match(/t\.string :password_digest/, up)
+ end
+ end
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_instance_method :user_params, content do |m|
+ assert_match(/permit\(:name, :password, :password_confirmation\)/, m)
+ end
+ end
+
+ assert_file "app/views/users/_form.html.erb" do |content|
+ assert_match(/<%= f\.password_field :password %>/, content)
+ assert_match(/<%= f\.password_field :password_confirmation %>/, content)
+ end
+
+ assert_file "app/views/users/index.html.erb" do |content|
+ assert_no_match(/password/, content)
+ end
+
+ assert_file "app/views/users/show.html.erb" do |content|
+ assert_no_match(/password/, content)
+ end
+
+ assert_file "test/controllers/users_controller_test.rb" do |content|
+ assert_match(/password: 'secret'/, content)
+ assert_match(/password_confirmation: 'secret'/, content)
+ end
+
+ assert_file "test/fixtures/users.yml" do |content|
+ assert_match(/password_digest: <%= BCrypt::Password.create\('secret'\) %>/, content)
+ end
+ end
end
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index b9fb071d23..5b9088cb64 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -38,7 +38,7 @@ class InfoTest < ActiveSupport::TestCase
end
def test_framework_version
- assert_property 'Active Support version', ActiveSupport::VERSION::STRING
+ assert_property 'Active Support version', ActiveSupport.version.to_s
end
def test_frameworks_exist
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 37d0be107c..26b388b6f9 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -105,7 +105,7 @@ module RailtiesTest
assert_not_nil bukkits_migration_order, "Expected migration to be skipped"
migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length
- output = `bundle exec rake railties:install:migrations`
+ `bundle exec rake railties:install:migrations`
assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length
end
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index c80b0f63af..0786b8f8c7 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -32,7 +32,7 @@ module RailtiesTest
end
end
- test "railtie_name can be set manualy" do
+ test "railtie_name can be set manually" do
class Foo < Rails::Railtie
railtie_name "bar"
end
diff --git a/tasks/release.rb b/tasks/release.rb
index 650b381e0f..cf5b6d6843 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -23,20 +23,8 @@ directory "pkg"
file = Dir[glob].first
ruby = File.read(file)
- major, minor, tiny, pre = version.split('.')
- pre = pre ? pre.inspect : "nil"
-
- ruby.gsub!(/^(\s*)MAJOR = .*?$/, "\\1MAJOR = #{major}")
- raise "Could not insert MAJOR in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)MINOR = .*?$/, "\\1MINOR = #{minor}")
- raise "Could not insert MINOR in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)TINY = .*?$/, "\\1TINY = #{tiny}")
- raise "Could not insert TINY in #{file}" unless $1
-
- ruby.gsub!(/^(\s*)PRE = .*?$/, "\\1PRE = #{pre}")
- raise "Could not insert PRE in #{file}" unless $1
+ ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
+ raise "Could not insert Gem::Version in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
end
diff --git a/version.rb b/version.rb
index 87fc7690ac..367d0f6546 100644
--- a/version.rb
+++ b/version.rb
@@ -1,10 +1,10 @@
module Rails
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ def self.version
+ Gem::Version.new "4.0.0.beta1"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = Rails.version.segments
+ STRING = Rails.version.to_s
end
end