aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Gemfile4
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--Rakefile93
-rw-r--r--actionmailer/README.rdoc4
-rw-r--r--actionmailer/lib/action_mailer/version.rb13
-rw-r--r--actionmailer/test/asset_host_test.rb1
-rw-r--r--actionmailer/test/url_test.rb9
-rw-r--r--actionpack/CHANGELOG.md53
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb14
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb1
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb29
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb5
-rw-r--r--actionpack/lib/action_controller/test_case.rb8
-rw-r--r--actionpack/lib/action_dispatch.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb302
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb96
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb10
-rw-r--r--actionpack/lib/action_dispatch/routing.rb92
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb51
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb13
-rw-r--r--actionpack/lib/action_view/buffers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb73
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb15
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb24
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb32
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner/html/selector.rb16
-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/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/flash_test.rb3
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb3
-rw-r--r--actionpack/test/controller/layout_test.rb24
-rw-r--r--actionpack/test/controller/live_stream_test.rb76
-rw-r--r--actionpack/test/controller/new_base/render_partial_test.rb22
-rw-r--r--actionpack/test/controller/new_base/render_test.rb24
-rw-r--r--actionpack/test/controller/routing_test.rb19
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb16
-rw-r--r--actionpack/test/dispatch/cookies_test.rb154
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb2
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb88
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb3
-rw-r--r--actionpack/test/fixtures/public/images/rails.pngbin1787 -> 0 bytes
-rw-r--r--actionpack/test/fixtures/test/change_priority.html.erb (renamed from actionpack/test/fixtures/test/change_priorty.html.erb)0
-rw-r--r--actionpack/test/template/form_helper_test.rb10
-rw-r--r--actionpack/test/template/form_options_helper_test.rb51
-rw-r--r--actionpack/test/template/render_test.rb2
-rw-r--r--actionpack/test/template/url_helper_test.rb24
-rw-r--r--activemodel/CHANGELOG.md18
-rw-r--r--activemodel/README.rdoc2
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb16
-rw-r--r--activemodel/lib/active_model/secure_password.rb23
-rw-r--r--activemodel/lib/active_model/validations.rb5
-rw-r--r--activemodel/lib/active_model/version.rb13
-rw-r--r--activemodel/test/cases/errors_test.rb103
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb4
-rw-r--r--activerecord/CHANGELOG.md145
-rw-r--r--activerecord/README.rdoc4
-rw-r--r--activerecord/examples/associations.pngbin40623 -> 0 bytes
-rw-r--r--activerecord/lib/active_record.rb8
-rw-r--r--activerecord/lib/active_record/associations.rb10
-rw-r--r--activerecord/lib/active_record/associations/association.rb10
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb1
-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.rb15
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-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/join_dependency.rb10
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb8
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb22
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb112
-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.rb76
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb80
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb64
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-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.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_handling.rb10
-rw-r--r--activerecord/lib/active_record/core.rb32
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb20
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb64
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake69
-rw-r--r--activerecord/lib/active_record/reflection.rb32
-rw-r--r--activerecord/lib/active_record/relation.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb8
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb41
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/scoping.rb62
-rw-r--r--activerecord/lib/active_record/scoping/default.rb23
-rw-r--r--activerecord/lib/active_record/scoping/named.rb9
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb10
-rw-r--r--activerecord/lib/active_record/tasks/firebird_database_tasks.rb56
-rw-r--r--activerecord/lib/active_record/tasks/oracle_database_tasks.rb45
-rw-r--r--activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb48
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/version.rb13
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_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/enum_test.rb2
-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/schema_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb6
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb13
-rw-r--r--activerecord/test/cases/aggregations_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb32
-rw-r--r--activerecord/test/cases/associations_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb91
-rw-r--r--activerecord/test/cases/batches_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb30
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb14
-rw-r--r--activerecord/test/cases/column_definition_test.rb13
-rw-r--r--activerecord/test/cases/column_test.rb36
-rw-r--r--activerecord/test/cases/counter_cache_test.rb9
-rw-r--r--activerecord/test/cases/dup_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/locking_test.rb8
-rw-r--r--activerecord/test/cases/migration/columns_test.rb13
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb47
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb21
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb (renamed from activerecord/test/cases/relation_scoping_test.rb)334
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb (renamed from activerecord/test/cases/named_scope_test.rb)20
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb331
-rw-r--r--activerecord/test/cases/tasks/firebird_rake_test.rb100
-rw-r--r--activerecord/test/cases/tasks/oracle_rake_test.rb93
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/cases/tasks/sqlserver_rake_test.rb87
-rw-r--r--activerecord/test/cases/timestamp_test.rb46
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/fixtures/dog_lovers.yml3
-rw-r--r--activerecord/test/fixtures/dogs.yml1
-rw-r--r--activerecord/test/fixtures/pets.yml5
-rw-r--r--activerecord/test/fixtures/toys.yml10
-rw-r--r--activerecord/test/models/dog.rb5
-rw-r--r--activerecord/test/models/dog_lover.rb5
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/pet.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb12
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb12
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb1
-rw-r--r--activerecord/test/schema/schema.rb9
-rw-r--r--activesupport/CHANGELOG.md20
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/callbacks.rb2
-rw-r--r--activesupport/lib/active_support/core_ext.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/indent.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/xchar.rb18
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/key_generator.rb2
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb5
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/notifications.rb28
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb4
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb2
-rw-r--r--activesupport/lib/active_support/version.rb13
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb2
-rw-r--r--activesupport/test/core_ext/class/attribute_test.rb9
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb6
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb6
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb8
-rw-r--r--activesupport/test/notifications_test.rb2
-rw-r--r--activesupport/test/ordered_options_test.rb20
-rw-r--r--activesupport/test/testing/constant_lookup_test.rb10
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb9
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb11
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb9
-rwxr-xr-xci/travis.rb4
-rw-r--r--guides/bug_report_templates/active_record_gem.rb37
-rw-r--r--guides/bug_report_templates/active_record_master.rb48
-rw-r--r--guides/code/getting_started/Gemfile26
-rw-r--r--guides/code/getting_started/app/assets/images/rails.pngbin6646 -> 0 bytes
-rw-r--r--guides/code/getting_started/config/application.rb5
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb2
-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.md34
-rw-r--r--guides/source/action_mailer_basics.md27
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_record_querying.md14
-rw-r--r--guides/source/active_record_validations.md41
-rw-r--r--guides/source/active_support_core_extensions.md6
-rw-r--r--guides/source/active_support_instrumentation.md10
-rw-r--r--guides/source/asset_pipeline.md32
-rw-r--r--guides/source/association_basics.md38
-rw-r--r--guides/source/command_line.md10
-rw-r--r--guides/source/configuring.md4
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md41
-rw-r--r--guides/source/debugging_rails_applications.md28
-rw-r--r--guides/source/engines.md9
-rw-r--r--guides/source/form_helpers.md28
-rw-r--r--guides/source/generators.md4
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/migrations.md4
-rw-r--r--guides/source/routing.md4
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/testing.md78
-rw-r--r--guides/source/upgrading_ruby_on_rails.md50
-rw-r--r--guides/source/working_with_javascript_in_rails.md1
-rw-r--r--rails.gemspec4
-rw-r--r--railties/CHANGELOG.md112
-rw-r--r--railties/RDOC_MAIN.rdoc73
-rw-r--r--railties/Rakefile7
-rw-r--r--railties/lib/rails/all.rb4
-rw-r--r--railties/lib/rails/api/task.rb158
-rw-r--r--railties/lib/rails/application.rb17
-rw-r--r--railties/lib/rails/commands.rb9
-rw-r--r--railties/lib/rails/commands/test_runner.rb146
-rw-r--r--railties/lib/rails/engine.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb76
-rw-r--r--railties/lib/rails/generators/named_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.pngbin6646 -> 0 bytes
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.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.rb5
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE26
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_case.rb217
-rw-r--r--railties/lib/rails/generators/testing/assertions.rb121
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb106
-rw-r--r--railties/lib/rails/generators/testing/setup_and_teardown.rb18
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/tasks/documentation.rake48
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb2
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb79
-rw-r--r--railties/lib/rails/test_unit/testing.rake59
-rw-r--r--railties/lib/rails/version.rb4
-rw-r--r--railties/railties.gemspec5
-rw-r--r--railties/test/application/asset_debugging_test.rb2
-rw-r--r--railties/test/application/assets_test.rb15
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/application/console_test.rb19
-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/middleware/remote_ip_test.rb3
-rw-r--r--railties/test/application/middleware/session_test.rb68
-rw-r--r--railties/test/application/rake_test.rb21
-rw-r--r--railties/test/application/test_runner_test.rb90
-rw-r--r--railties/test/commands/console_test.rb5
-rw-r--r--railties/test/commands/dbconsole_test.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb14
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb6
-rw-r--r--railties/test/generators_test.rb2
-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--railties/test/test_info_test.rb59
-rw-r--r--tasks/release.rb25
-rw-r--r--version.rb4
329 files changed, 5047 insertions, 2590 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/Gemfile b/Gemfile
index a367397c89..9c6ca111d2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,10 +11,10 @@ gem 'coffee-rails', '~> 4.0.0.beta1'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
-gem 'uglifier', require: false
+gem 'uglifier', '~> 1.3', require: false
group :doc do
- gem 'sdoc', github: 'voloko/sdoc'
+ gem 'sdoc'
gem 'redcarpet', '~> 2.2.2', platforms: :ruby
gem 'w3c_validators'
gem 'kindlerb'
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/README.rdoc b/actionmailer/README.rdoc
index 9feb2add5b..4d78d6142a 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -1,6 +1,6 @@
= Action Mailer -- Easy email delivery and testing
-Action Mailer is a framework for designing email-service layers. These layers
+Action Mailer is a framework for designing email service layers. These layers
are used to consolidate code for sending out forgotten passwords, welcome
wishes on signup, invoices for billing, and any other use case that requires
a written notification to either a person or another system.
@@ -78,7 +78,7 @@ Or you can just chain the methods together like:
It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
-Note that every value you set with this method will get over written if you use the same key in your mailer method.
+Note that every value you set with this method will get overwritten if you use the same key in your mailer method.
Example:
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/asset_host_test.rb b/actionmailer/test/asset_host_test.rb
index 32ae76a7c5..00f1348a53 100644
--- a/actionmailer/test/asset_host_test.rb
+++ b/actionmailer/test/asset_host_test.rb
@@ -16,7 +16,6 @@ class AssetHostTest < ActiveSupport::TestCase
ActionMailer::Base.deliveries.clear
AssetHostMailer.configure do |c|
c.asset_host = "http://www.example.com"
- c.assets_dir = ''
end
end
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index dcd80f46b5..589944fa69 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -41,18 +41,9 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
def setup
- set_delivery_method :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries.clear
- ActiveSupport::Deprecation.silenced = false
-
@recipient = 'test@localhost'
end
- def teardown
- restore_delivery_method
- end
-
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 0c6973f9b6..913edbd8df 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,57 @@
## Rails 4.0.0 (unreleased) ##
+* Fix explicit names on multiple file fields. If a file field tag has
+ the multiple option, it is turned into an array field (appending `[]`),
+ but if an explicit name is passed to `file_field` the `[]` is not
+ appended.
+ Fixes #9830.
+
+ *Ryan McGeary*
+
+* Add block support for the `mail_to` helper, similar to the `link_to` helper.
+
+ *Sam Pohlenz*
+
+* Automatically configure cookie-based sessions to be encrypted if
+ `secret_key_base` is set, falling back to signed if only `secret_token`
+ is set. Automatically upgrade existing signed cookie-based sessions from
+ Rails 3.x to be encrypted if both `secret_key_base` and `secret_token`
+ are set, or signed with the new key generator if only `secret_token` is
+ set. This leaves only the `config.session_store :cookie_store` option and
+ removes the two new options introduced in 4.0.0.beta1:
+ `encrypted_cookie_store` and `upgrade_signature_to_encryption_cookie_store`.
+
+ *Trevor Turk*
+
+* 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. To get the "no layout" behavior just
+ return `false` instead of `nil` for `layout`.
+
+ *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" }
@@ -95,6 +147,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*
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/base.rb b/actionpack/lib/abstract_controller/base.rb
index 56dc9ab7a1..af5de815bb 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -35,7 +35,7 @@ module AbstractController
end
def inherited(klass) # :nodoc:
- # define the abstract ivar on subclasses so that we don't get
+ # Define the abstract ivar on subclasses so that we don't get
# uninitialized ivar warnings
unless klass.instance_variable_defined?(:@abstract)
klass.instance_variable_set(:@abstract, false)
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 812a35735f..5ae8c6c3b0 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -29,7 +29,7 @@ module AbstractController
# helper_method :current_user, :logged_in?
#
# def current_user
- # @current_user ||= User.find_by_id(session[:user])
+ # @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
@@ -59,7 +59,7 @@ module AbstractController
# The +helper+ class method can take a series of helper module names, a block, or both.
#
# ==== Options
- # * <tt>*args</tt> - Module, Symbol, String, :all
+ # * <tt>*args</tt> - Module, Symbol, String
# * <tt>block</tt> - A block defining helper methods
#
# When the argument is a module it will be included directly in the template class.
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..3f34add790 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -105,8 +105,8 @@ 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
- # overriden in order to still return a string.
+ # responds to the method each), this method needs to be
+ # overridden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 35facd13c8..243fd40a7e 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -94,7 +94,6 @@ module ActionController
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
names.sort!
- names
end
helpers.uniq!
helpers
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index c7bb2dd147..158d552ec7 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -29,7 +29,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -345,7 +345,7 @@ module ActionController
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fb664a69dd..8092fd639f 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -34,6 +34,7 @@ module ActionController
module Live
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
+ @error_callback = nil
super(response, SizedQueue.new(10))
end
@@ -56,6 +57,14 @@ module ActionController
super
@buf.push nil
end
+
+ def on_error(&block)
+ @error_callback = block
+ end
+
+ def call_on_error
+ @error_callback.call
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
@@ -121,6 +130,16 @@ module ActionController
begin
super(name)
+ rescue => e
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
ensure
@_response.commit!
end
@@ -129,6 +148,16 @@ module ActionController
@_response.await_commit
end
+ def log_error(exception)
+ logger = ActionController::Base.logger
+ return unless logger
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+
def response_body=(body)
super
response.stream.close if response
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index acad8a0799..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
#
@@ -418,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 41b5228872..a35a613158 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -18,7 +18,7 @@ module ActionController
@_layouts = Hash.new(0)
@_files = Hash.new(0)
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -28,7 +28,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -41,7 +41,7 @@ module ActionController
@_templates[path] += 1
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
next if payload[:virtual_path] # files don't have virtual path
path = payload[:identifier]
@@ -316,7 +316,7 @@ module ActionController
# assert_response :found
#
# # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
+ # assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 618e2f3033..24a3d4741e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -82,12 +82,10 @@ module ActionDispatch
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 912da741b7..049840e2e1 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -223,8 +223,8 @@ module Mime
Mime.instance_eval { remove_const(symbol) }
SET.delete_if { |v| v.eql?(mime) }
- LOOKUP.delete_if { |k,v| v.eql?(mime) }
- EXTENSION_LOOKUP.delete_if { |k,v| v.eql?(mime) }
+ LOOKUP.delete_if { |_,v| v.eql?(mime) }
+ EXTENSION_LOOKUP.delete_if { |_,v| v.eql?(mime) }
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 319d0197d1..b57c84dec8 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -6,7 +6,7 @@ module ActionDispatch
# of its interface is available directly for convenience.
#
# Uploaded files are temporary files whose lifespan is one request. When
- # the object is finalized Ruby unlinks the file, so there is not need to
+ # the object is finalized Ruby unlinks the file, so there is no need to
# clean them with a separate maintenance task.
class UploadedFile
# The basename of the file in the client.
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 82c55660ea..a732e570f2 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -58,7 +58,7 @@ module ActionDispatch
end
end
- parameterized_parts.keep_if { |_, v| v }
+ parameterized_parts.keep_if { |_, v| v }
parameterized_parts
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 36a0db6e61..5b914f293d 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'
@@ -30,7 +31,7 @@ module ActionDispatch
#
# # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
+ # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -52,13 +53,13 @@ module ActionDispatch
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
#
- # cookies[:key] = {
+ # cookies[:name] = {
# value: 'a yummy cookie',
# expires: 1.year.from_now,
# domain: 'domain.com'
# }
#
- # cookies.delete(:key, domain: 'domain.com')
+ # cookies.delete(:name, domain: 'domain.com')
#
# The option symbols for setting cookies are:
#
@@ -69,7 +70,7 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting keys.
+ # <tt>:all</tt> again when deleting cookies.
#
# domain: nil # Does not sets cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
@@ -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,99 @@ 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.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.signed[:discount] = 45
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+ #
+ # cookies.signed[:discount] # => 45
+ def signed
+ @signed ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ else
+ SignedCookieJar.new(self, @key_generator, @options)
+ end
+ 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.
+ #
+ # If +config.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # legacy cookies signed with the old key generator will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+.
+ #
+ # Example:
+ #
+ # cookies.encrypted[:discount] = 45
+ # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ #
+ # cookies.encrypted[:discount] # => 45
+ def encrypted
+ @encrypted ||=
+ if @options[:upgrade_legacy_signed_cookies]
+ UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ else
+ EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+ end
+
+ # Returns the +signed+ or +encrypted jar, preferring +encrypted+ if +secret_key_base+ is set.
+ # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
+ def signed_or_encrypted
+ @signed_or_encrypted ||=
+ if @options[:secret_key_base].present?
+ encrypted
+ else
+ signed
+ end
+ end
+ end
+
+ module VerifyAndUpgradeLegacySignedMessage
+ def initialize(*args)
+ super
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ end
+
+ 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 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 +208,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_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ }
end
def self.build(request)
@@ -184,7 +280,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -195,10 +291,10 @@ module ActionDispatch
handle_options(options)
- if @cookies[key.to_s] != value or options[:expires]
- @cookies[key.to_s] = value
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
+ if @cookies[name.to_s] != value or options[:expires]
+ @cookies[name.to_s] = value
+ @set_cookies[name.to_s] = options
+ @delete_cookies.delete(name.to_s)
end
value
@@ -207,24 +303,24 @@ module ActionDispatch
# Removes the cookie on the client machine by setting the value to an empty string
# and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
- def delete(key, options = {})
- return unless @cookies.has_key? key.to_s
+ def delete(name, options = {})
+ return unless @cookies.has_key? name.to_s
options.symbolize_keys!
handle_options(options)
- value = @cookies.delete(key.to_s)
- @delete_cookies[key.to_s] = options
+ value = @cookies.delete(name.to_s)
+ @delete_cookies[name.to_s] = options
value
end
# Whether the given cookie is to be deleted by this CookieJar.
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
- def deleted?(key, options = {})
+ def deleted?(name, options = {})
options.symbolize_keys!
handle_options(options)
- @delete_cookies[key.to_s] == options
+ @delete_cookies[name.to_s] == options
end
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
@@ -232,59 +328,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) }
@@ -299,24 +342,25 @@ module ActionDispatch
self.always_write_cookie = false
private
-
def write_cookie?(cookie)
@secure || !cookie[:secure] || always_write_cookie
end
end
class PermanentCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@options = options
end
- def [](key)
+ def [](name)
@parent_jar[name.to_s]
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -324,28 +368,13 @@ module ActionDispatch
end
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."
+ @parent_jar[name] = options
end
end
class SignedCookieJar #:nodoc:
+ include ChainedCookieJars
+
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
@@ -355,13 +384,11 @@ module ActionDispatch
def [](name)
if signed_message = @parent_jar[name]
- @verifier.verify(signed_message)
+ verify(signed_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -370,32 +397,38 @@ module ActionDispatch
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
+ private
+ def verify(signed_message)
+ @verifier.verify(signed_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ nil
+ end
+ end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
+ # 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:
+ include VerifyAndUpgradeLegacySignedMessage
- 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 [](name)
+ if signed_message = @parent_jar[name]
+ verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ end
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." +
- "Set config.secret_key_base in config/initializers/secret_token.rb"
+ if ActiveSupport::LegacyKeyGenerator === key_generator
+ raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
+ "Read the upgrade documentation to learn more about this new config option."
end
@parent_jar = parent_jar
@@ -405,16 +438,13 @@ module ActionDispatch
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
end
- def [](key)
- if encrypted_message = @parent_jar[key]
- @encryptor.decrypt_and_verify(encrypted_message)
+ def [](name)
+ if encrypted_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_message)
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature,
- ActiveSupport::MessageEncryptor::InvalidMessage
- nil
end
- def []=(key, options)
+ def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -423,24 +453,28 @@ module ActionDispatch
options[:value] = @encryptor.encrypt_and_sign(options[:value])
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
- @parent_jar[key] = options
+ @parent_jar[name] = options
end
- def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
- end
-
- def signed
- @signed ||= SignedCookieJar.new(self, @key_generator, @options)
- end
+ private
+ def decrypt_and_verify(encrypted_message)
+ @encryptor.decrypt_and_verify(encrypted_message)
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+ end
- def encrypted
- @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
- end
+ # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
+ # instead of EncryptedCookieJar if config.secret_token and config.secret_key_base
+ # are both set. It reads legacy cookies signed with the old dummy key generator and
+ # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
+ class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
+ include VerifyAndUpgradeLegacySignedMessage
- 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 [](name)
+ if encrypted_or_signed_message = @parent_jar[name]
+ decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ end
end
end
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/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 44290445d4..5d1740d0d4 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -18,7 +18,7 @@ module ActionDispatch
def call(env)
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
+ @app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 1e6ed624b0..b9eb8036e9 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -4,36 +4,51 @@ require 'rack/session/cookie'
module ActionDispatch
module Session
- # This cookie-based session store is the Rails default. Sessions typically
- # contain at most a user_id and flash message; both fit within the 4K cookie
- # size limit. Cookie-based sessions are dramatically faster than the
- # alternatives.
+ # This cookie-based session store is the Rails default. It is
+ # dramatically faster than the alternatives.
#
- # If you have more than 4K of session data or don't want your data to be
- # visible to the user, pick another session store.
+ # Sessions typically contain at most a user_id and flash message; both fit
+ # within the 4K cookie size limit. A CookieOverflow exception is raised if
+ # you attempt to store more than 4K of data.
#
- # CookieOverflow is raised if you attempt to store more than 4K of data.
+ # The cookie jar used for storage is automatically configured to be the
+ # best possible option given your application's configuration.
#
- # A message digest is included with the cookie to ensure data integrity:
- # a user cannot alter his +user_id+ without knowing the secret key
- # included in the hash. New apps are generated with a pregenerated secret
- # in config/environment.rb. Set your own for old apps you're upgrading.
+ # If you only have secret_token set, your cookies will be signed, but
+ # not encrypted. This means a user cannot alter his +user_id+ without
+ # knowing your app's secret key, but can easily read his +user_id+. This
+ # was the default for Rails 3 apps.
#
- # Session options:
+ # If you have secret_key_base set, your cookies will be encrypted. This
+ # goes a step further than signed cookies in that encrypted cookies cannot
+ # be altered or read by users. This is the default starting in Rails 4.
#
- # * <tt>:secret</tt>: An application-wide key string. It's important that
- # the secret is not vulnerable to a dictionary attack. Therefore, you
- # should choose a secret consisting of random numbers and letters and
- # more than 30 characters.
+ # If you have both secret_token and secret_key base set, your cookies will
+ # be encrypted, and signed cookies generated by Rails 3 will be
+ # transparently read and encrypted to provide a smooth upgrade path.
#
- # secret: '449fe2e7daee471bffae2fd8dc02313d'
+ # Configure your session store in config/initializers/session_store.rb:
#
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
- # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
+ # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
#
- # To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/initializers/secret_token.rb.
+ # Configure your secret key in config/initializers/secret_token.rb:
+ #
+ # Myapp::Application.config.secret_key_base 'secret key'
+ #
+ # To generate a secret key for an existing application, run `rake secret`.
+ #
+ # If you are upgrading an existing Rails 3 app, you should leave your
+ # existing secret_token in place and simply add the new secret_key_base.
+ # Note that you should wait to set secret_key_base until you have 100% of
+ # your userbase on Rails 4 and are reasonably sure you will not need to
+ # rollback to Rails 3. This is because cookies signed based on the new
+ # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
+ # 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. Additionally,
+ # you should take care to make sure you are not relying on the ability to
+ # decode signed cookies generated by your app in external applications or
+ # Javascript before upgrading.
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
@@ -100,42 +115,7 @@ module ActionDispatch
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed
- end
- end
-
- class EncryptedCookieStore < CookieStore
-
- private
-
- def cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.encrypted
- end
- end
-
- # This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
- # To use this CookieStore set
- #
- # Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
- #
- # in your config/initializers/session_store.rb
- #
- # You will also need to add
- #
- # Myapp::Application.config.secret_key_base = 'some secret'
- #
- # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
- class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
- private
-
- def get_cookie(env)
- signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
- end
-
- def signed_using_old_secret_cookie_jar(env)
- request = ActionDispatch::Request.new(env)
- request.cookie_jar.signed_using_old_secret
+ request.cookie_jar.signed_or_encrypted
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 550f4dbd0d..db219c8fa9 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -13,7 +13,7 @@
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
def debug_hash(object)
- object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
end unless self.class.method_defined?(:debug_hash)
%>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
index 9d947aea40..b181909bff 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
@@ -1,10 +1,8 @@
<%
- traces = [
- ["Application Trace", @application_trace],
- ["Framework Trace", @framework_trace],
- ["Full Trace", @full_trace]
- ]
- names = traces.collect {|name, trace| name}
+ traces = { "Application Trace" => @application_trace,
+ "Framework Trace" => @framework_trace,
+ "Full Trace" => @full_trace }
+ names = traces.keys
%>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index d55eb8109a..550c7d0e7b 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -69,6 +69,22 @@ module ActionDispatch
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
# <tt>Routing::Mapper::Scoping#scope</tt>.
#
+ # == Non-resourceful routes
+ #
+ # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
+ #
+ # get 'post/:id' => 'posts#show'
+ # post 'post/:id' => 'posts#create_comment'
+ #
+ # If your route needs to respond to more than one HTTP method (or all methods) then using the
+ # <tt>:via</tt> option on <tt>match</tt> is preferable.
+ #
+ # match 'post/:id' => 'posts#show', via: [:get, :post]
+ #
+ # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
+ # URL will route to the <tt>show</tt> action.
+ #
# == Named routes
#
# Routes can be named by passing an <tt>:as</tt> option,
@@ -78,7 +94,7 @@ module ActionDispatch
# Example:
#
# # In routes.rb
- # match '/login' => 'accounts#login', as: 'login'
+ # get '/login' => 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
@@ -104,9 +120,9 @@ module ActionDispatch
#
# # In routes.rb
# controller :blog do
- # match 'blog/show' => :list
- # match 'blog/delete' => :delete
- # match 'blog/edit/:id' => :edit
+ # get 'blog/show' => :list
+ # get 'blog/delete' => :delete
+ # get 'blog/edit/:id' => :edit
# end
#
# # provides named routes for show, delete, and edit
@@ -116,7 +132,7 @@ module ActionDispatch
#
# Routes can generate pretty URLs. For example:
#
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
+ # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
# year: /\d{4}/,
# month: /\d{1,2}/,
# day: /\d{1,2}/
@@ -131,7 +147,7 @@ module ActionDispatch
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /\d{5}(-\d{4})?/
# }
#
@@ -139,13 +155,13 @@ module ActionDispatch
# expression modifiers:
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode' => :show, constraints: {
# postalcode: /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
@@ -153,73 +169,21 @@ module ActionDispatch
# }
# end
#
- # Using the multiline match modifier will raise an +ArgumentError+.
+ # Using the multiline modifier will raise an +ArgumentError+.
# Encoding regular expression modifiers are silently ignored. The
# match will always use the default encoding or ASCII.
#
- # == Default route
- #
- # Consider the following route, which you will find commented out at the
- # bottom of your generated <tt>config/routes.rb</tt>:
- #
- # match ':controller(/:action(/:id))(.:format)'
- #
- # This route states that it expects requests to consist of a
- # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
- # turn is followed optionally by an <tt>:id</tt>, which in turn is followed
- # optionally by a <tt>:format</tt>.
- #
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
- # up with:
- #
- # params = { controller: 'blog',
- # action: 'edit',
- # id: '22'
- # }
- #
- # By not relying on default routes, you improve the security of your
- # application since not all controller actions, which includes actions you
- # might add at a later time, are exposed by default.
- #
- # == HTTP Methods
- #
- # Using the <tt>:via</tt> option when specifying a route allows you to
- # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
- # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
- # <tt>:any</tt>. If your route needs to respond to more than one method you
- # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
- # <tt>:any</tt> which means that the route will respond to any of the HTTP
- # methods.
- #
- # match 'post/:id' => 'posts#show', via: :get
- # match 'post/:id' => 'posts#create_comment', via: :post
- #
- # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
- # URL will route to the <tt>show</tt> action.
- #
- # === HTTP helper methods
- #
- # An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
- #
- # get 'post/:id' => 'posts#show'
- # post 'post/:id' => 'posts#create_comment'
- #
- # This syntax is less verbose and the intention is more apparent to someone else reading your code,
- # however if your route needs to respond to more than one HTTP method (or all methods) then using the
- # <tt>:via</tt> option on <tt>match</tt> is preferable.
- #
# == External redirects
#
# You can redirect any path to another path using the redirect helper in your router:
#
- # match "/stories" => redirect("/posts")
+ # get "/stories" => redirect("/posts")
#
# == Unicode character routes
#
# You can specify unicode character routes in your router:
#
- # match "こんにちは" => "welcome#index"
+ # get "こんにちは" => "welcome#index"
#
# == Routing to Rack Applications
#
@@ -227,7 +191,7 @@ module ActionDispatch
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
- # match "/application.js" => Sprockets
+ # get "/application.js" => Sprockets
#
# == Reloading routes
#
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index c5f2b33602..80054c8a40 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -10,6 +10,9 @@ module ActionDispatch
module Routing
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
+ SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :path_names, :constraints, :defaults,
+ :shallow, :blocks, :options]
class Constraints #:nodoc:
def self.new(app, constraints, request = Rack::Request)
@@ -58,8 +61,8 @@ module ActionDispatch
@set, @scope, @path, @options = set, scope, path, options
@requirements, @conditions, @defaults = {}, {}, {}
- normalize_path!
normalize_options!
+ normalize_path!
normalize_requirements!
normalize_conditions!
normalize_defaults!
@@ -486,7 +489,7 @@ module ActionDispatch
end
options = app
- app, path = options.find { |k, v| k.respond_to?(:call) }
+ app, path = options.find { |k, _| k.respond_to?(:call) }
options.delete(app) if app
end
@@ -589,8 +592,7 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
- options[:path] ||= args.first if args.first.is_a?(String)
+ options[:via] = method
match(*args, options, &block)
self
end
@@ -698,19 +700,21 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
- scope_options.each do |option|
- if value = options.delete(option)
+ SCOPE_OPTIONS.each do |option|
+ if option == :blocks
+ value = block
+ elsif option == :options
+ value = options
+ else
+ value = options.delete(option)
+ end
+
+ if value
recover[option] = @scope[option]
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
- recover[:blocks] = @scope[:blocks]
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
-
- recover[:options] = @scope[:options]
- @scope[:options] = merge_options_scope(@scope[:options], options)
-
yield
self
ensure
@@ -841,10 +845,6 @@ module ActionDispatch
end
private
- def scope_options #:nodoc:
- @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
- end
-
def merge_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
@@ -1361,7 +1361,7 @@ module ActionDispatch
def match(path, *rest)
if rest.empty? && Hash === path
options = path
- path, to = options.find { |name, value| name.is_a?(String) }
+ path, to = options.find { |name, _value| name.is_a?(String) }
options[:to] = to
options.delete(path)
paths = [path]
@@ -1370,18 +1370,23 @@ module ActionDispatch
paths = [path] + rest
end
- path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, options)
- options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
- end
-
options[:anchor] = true unless options.key?(:anchor)
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- paths.each { |_path| decomposed_match(_path, options.dup) }
+ paths.each do |_path|
+ route_options = options.dup
+ route_options[:path] ||= _path if _path.is_a?(String)
+
+ path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, route_options)
+ route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ end
+
+ decomposed_match(_path, route_options)
+ end
self
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 619dd22ec1..d48a83e6c6 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
@@ -657,7 +665,7 @@ module ActionDispatch
end
req = @request_class.new(env)
- @router.recognize(req) do |route, matches, params|
+ @router.recognize(req) do |route, _matches, params|
params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 9210bffd1d..496682e8bd 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -81,7 +81,7 @@ module ActionDispatch
# Load routes.rb if it hasn't been loaded.
generated_path, extra_keys = @routes.generate_extras(options, defaults)
- found_extras = options.reject {|k, v| ! extra_keys.include? k}
+ found_extras = options.reject { |k, _| ! extra_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
@@ -120,7 +120,7 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
- generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) }
+ generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index e481f3b245..3253a3d424 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -377,8 +377,8 @@ module ActionDispatch
node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
end
- selected = elements.map do |_element|
- text = _element.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
+ selected = elements.map do |elem|
+ text = elem.children.select{ |c| not c.tag? }.map{ |c| fix_content[c] }.join
root = HTML::Document.new(CGI.unescapeHTML("<encoded>#{text}</encoded>")).root
css_select(root, "encoded:root", &block)[0]
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index e657283cec..630e6a9b78 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -6,7 +6,7 @@ module ActionDispatch
module TestProcess
def assigns(key = nil)
assigns = {}.with_indifferent_access
- @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
+ @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
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/buffers.rb b/actionpack/lib/action_view/buffers.rb
index 2372d3c433..361a0dccbe 100644
--- a/actionpack/lib/action_view/buffers.rb
+++ b/actionpack/lib/action_view/buffers.rb
@@ -8,9 +8,15 @@ module ActionView
end
def <<(value)
+ return self if value.nil?
super(value.to_s)
end
alias :append= :<<
+
+ def safe_concat(value)
+ return self if value.nil?
+ super(value.to_s)
+ end
alias :safe_append= :safe_concat
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 31e37893c6..521eaf25d2 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -58,7 +58,7 @@ module ActionView
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
- }.merge(options)
+ }.merge!(options)
content_tag(:script, "", tag_options)
}.join("\n").html_safe
end
@@ -98,7 +98,7 @@ module ActionView
"rel" => "stylesheet",
"media" => "screen",
"href" => path_to_stylesheet(source, path_options)
- }.merge(options)
+ }.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
end
@@ -166,7 +166,7 @@ module ActionView
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
:href => path_to_image(source)
- }.merge(options.symbolize_keys))
+ }.merge!(options.symbolize_keys))
end
# Returns an HTML image tag for the +source+. The +source+ can be a full
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6b6a7edc1d..36dedf0676 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -655,14 +655,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
@@ -1160,12 +1152,65 @@ module ActionView
end
end
+ # A +FormBuilder+ object is associated with a particular model object and
+ # allows you to generate fields associated with the model object. The
+ # +FormBuilder+ object is yielded when using +form_for+ or +fields_for+.
+ # For example:
+ #
+ # <%= form_for @person do |person_form| %>
+ # Name: <%= person_form.text_field :name %>
+ # Admin: <%= person_form.check_box :admin %>
+ # <% end %>
+ #
+ # In the above block, the a +FormBuilder+ object is yielded as the
+ # +person_form+ variable. This allows you to generate the +text_field+
+ # and +check_box+ fields by specifying their eponymous methods, which
+ # modify the underlying template and associates the +@person+ model object
+ # with the form.
+ #
+ # The +FormBuilder+ object can be thought of as serving as a proxy for the
+ # methods in the +FormHelper+ module. This class, however, allows you to
+ # call methods with the model object you are building the form for.
+ #
+ # You can create your own custom FormBuilder templates by subclasses this
+ # class. For example:
+ #
+ # class MyFormBuilder < ActionView::Helpers::FormBuilder
+ # def div_radio_button(method, tag_value, options = {})
+ # @template.content_tag(:div,
+ # @template.radio_button(
+ # @object_name, method, tag_value, objectify_options(options)
+ # )
+ # )
+ # end
+ #
+ # The above code creates a new method +div_radio_button+ which wraps a div
+ # around the a new radio button. Note that when options are passed in, you
+ # must called +objectify_options+ in order for the model object to get
+ # correctly passed to the method. If +objectify_options+ is not called,
+ # then the newly created helper will not be linked back to the model.
+ #
+ # The +div_radio_button+ code from above can now be used as follows:
+ #
+ # <%= form_for @person, :builder => MyFormBuilder do |f| %>
+ # I am a child: <%= f.div_radio_button(:admin, "child") %>
+ # I am an adult: <%= f.div_radio_button(:admin, "adult") %>
+ # <% end -%>
+ #
+ # The standard set of helper methods for form building are located in the
+ # +field_helpers+ class attribute.
class FormBuilder
include ModelNaming
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class]
+ self.field_helpers = [:fields_for, :label, :text_field, :password_field,
+ :hidden_field, :file_field, :text_area, :check_box,
+ :radio_button, :color_field, :search_field,
+ :telephone_field, :phone_field, :date_field,
+ :time_field, :datetime_field, :datetime_local_field,
+ :month_field, :week_field, :url_field, :email_field,
+ :number_field, :range_field]
attr_accessor :object_name, :object, :options
@@ -1247,7 +1292,7 @@ module ActionView
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
- # <%= f.submit %>
+ # <%= person_form.submit %>
# <% end %>
#
# In this case, the checkbox field will be represented by an HTML +input+
@@ -1425,14 +1470,6 @@ module ActionView
# ...
# <% end %>
#
- # When projects is already an association on Person you can use
- # +accepts_nested_attributes_for+ to define the writer method for you:
- #
- # class Person < ActiveRecord::Base
- # has_many :projects
- # accepts_nested_attributes_for :projects
- # end
- #
# If you want to destroy any of the associated models through the
# form, you have to enable it first using the <tt>:allow_destroy</tt>
# option for +accepts_nested_attributes_for+:
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 377819a80c..7e65ebb4e4 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -565,7 +565,7 @@ module ActionView
if priority_zones
if priority_zones.is_a?(Regexp)
- priority_zones = zones.grep(priority_zones)
+ priority_zones = zones.select { |z| z =~ priority_zones }
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
@@ -752,7 +752,7 @@ module ActionView
end
def prompt_text(prompt)
- prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
end
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 878d3e0eda..edff98ddaa 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -81,7 +81,7 @@ module ActionView
# # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
#
def button_to_function(name, function=nil, html_options={})
- message = "button_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
+ message = "button_to_function is deprecated and will be removed from Rails 4.1. We recommend using Unobtrusive JavaScript instead. " +
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
ActiveSupport::Deprecation.warn message
@@ -103,7 +103,7 @@ module ActionView
# # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
#
def link_to_function(name, function, html_options={})
- message = "link_to_function is deprecated and will be removed from Rails 4.1. We recomend to use Unobtrusive JavaScript instead. " +
+ message = "link_to_function is deprecated and will be removed from Rails 4.1. We recommend using Unobtrusive JavaScript instead. " +
"See http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript"
ActiveSupport::Deprecation.warn message
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
index aef1572290..10dec66b4f 100644
--- a/actionpack/lib/action_view/helpers/tags/base.rb
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -73,27 +73,26 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
options.delete("index")
elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= options.fetch("name"){ tag_name }
+ options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
options["id"] = options.fetch("id"){ tag_id }
end
- options["name"] += "[]" if options["multiple"] && !options["name"].ends_with?("[]")
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
end
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
+ def tag_name(multiple = false)
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ def tag_name_with_index(index, multiple = false)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
def tag_id
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 775d93ed39..22059a0170 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -425,8 +425,8 @@ module ActionView
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Obfuscation
- # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
- # in order to hinder email harvesters. To take advantage of these options,
+ # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
+ # in order to hinder email harvesters. To take advantage of these options,
# install the +actionview-encoded_mail_to+ gem.
#
# ==== Examples
@@ -439,18 +439,30 @@ module ActionView
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
# subject: "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
- def mail_to(email_address, name = nil, html_options = {})
+ #
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
+ #
+ # <%= mail_to "me@domain.com" do %>
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # <% end %>
+ # # => <a href="mailto:me@domain.com">
+ # <strong>Email me:</strong> <span>me@domain.com</span>
+ # </a>
+ def mail_to(email_address, name = nil, html_options = {}, &block)
email_address = ERB::Util.html_escape(email_address)
- html_options.stringify_keys!
+ html_options, name = name, nil if block_given?
+ html_options = (html_options || {}).stringify_keys
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
- content_tag "a", name || email_address.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
+
+ html_options["href"] = "mailto:#{email_address}#{extras}".html_safe
+
+ content_tag(:a, name || email_address.html_safe, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 5aaafc15c1..7d7a7af51d 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -6,12 +6,23 @@ module ActionView
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
+ @newline_pending = 0
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
- src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
+
+ if text == "\n"
+ @newline_pending += 1
+ else
+ src << "@output_buffer.safe_append='"
+ src << "\n" * @newline_pending if @newline_pending > 0
+ src << escape_text(text)
+ src << "';"
+
+ @newline_pending = 0
+ end
end
# Erubis toggles <%= and <%== behavior when escaping is enabled.
@@ -28,24 +39,39 @@ module ActionView
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
+ flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
src << '@output_buffer.append= ' << code
else
- src << '@output_buffer.append= (' << code << ');'
+ src << '@output_buffer.append=(' << code << ');'
end
end
def add_expr_escaped(src, code)
+ flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
src << "@output_buffer.safe_append= " << code
else
- src << "@output_buffer.safe_concat((" << code << ").to_s);"
+ src << "@output_buffer.safe_append=(" << code << ");"
end
end
+ def add_stmt(src, code)
+ flush_newline_if_pending(src)
+ super
+ end
+
def add_postamble(src)
+ flush_newline_if_pending(src)
src << '@output_buffer.to_s'
end
+
+ def flush_newline_if_pending(src)
+ if @newline_pending > 0
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
+ @newline_pending = 0
+ end
+ end
end
class ERB
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 47bd011d5e..6b9b0a826b 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -255,7 +255,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # This one allows you to keep files with different formats in seperated subdirectories,
+ # This one allows you to keep files with different formats in separate subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
index 60b6783b19..7f8609c408 100644
--- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb
@@ -537,7 +537,7 @@ module HTML
# Get identifier, class, attribute name, pseudo or negation.
while true
# Element identifier.
- next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
+ next if statement.sub!(/^#(\?|[\w\-]+)/) do
id = $1
if id == "?"
id = values.shift
@@ -549,7 +549,7 @@ module HTML
end
# Class name.
- next if statement.sub!(/^\.([\w\-]+)/) do |match|
+ next if statement.sub!(/^\.([\w\-]+)/) do
class_name = $1
@source << ".#{class_name}"
class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
@@ -558,7 +558,7 @@ module HTML
end
# Attribute value.
- next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
+ next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do
name, equality, value = $1, $2, $3
if value == "?"
value = values.shift
@@ -575,7 +575,7 @@ module HTML
end
# Root element only.
- next if statement.sub!(/^:root/) do |match|
+ next if statement.sub!(/^:root/) do
pseudo << lambda do |element|
element.parent.nil? || !element.parent.tag?
end
@@ -611,7 +611,7 @@ module HTML
"" # Remove
end
# First/last child (of type).
- next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
+ next if statement.sub!(/^:(first|last)-(child|of-type)/) do
reverse = $1 == "last"
of_type = $2 == "of-type"
pseudo << nth_child(0, 1, of_type, reverse)
@@ -619,7 +619,7 @@ module HTML
"" # Remove
end
# Only child (of type).
- next if statement.sub!(/^:only-(child|of-type)/) do |match|
+ next if statement.sub!(/^:only-(child|of-type)/) do
of_type = $1 == "of-type"
pseudo << only_child(of_type)
@source << ":only-#{$1}"
@@ -628,7 +628,7 @@ module HTML
# Empty: no child elements or meaningful content (whitespaces
# are ignored).
- next if statement.sub!(/^:empty/) do |match|
+ next if statement.sub!(/^:empty/) do
pseudo << lambda do |element|
empty = true
for child in element.children
@@ -644,7 +644,7 @@ module HTML
end
# Content: match the text content of the element, stripping
# leading and trailing spaces.
- next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
+ next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do
content = $1
if content == "?"
content = values.shift
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/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 7157bccfb3..8213997f4e 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -332,7 +332,7 @@ end
module ActionDispatch
module RoutingVerbs
- def get(uri_or_host, path = nil, port = nil)
+ def get(uri_or_host, path = nil)
host = uri_or_host.host unless path
path ||= uri_or_host.path
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 9d4356f546..3b874a739a 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class FlashTest < ActionController::TestCase
@@ -219,7 +218,7 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
- Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+ Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
class TestController < ActionController::Base
add_flash_types :bar
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 4287856550..9f1c168209 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
@@ -43,7 +42,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
end
teardown do
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 5755444a65..34164a19f0 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -52,6 +52,29 @@ module ActionController
def with_stale
render :text => 'stale' if stale?(:etag => "123")
end
+
+ def exception_in_view
+ render 'doesntexist'
+ end
+
+ def exception_with_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+
+ response.stream.on_error do
+ response.stream.write %(data: "500 Internal Server Error"\n\n)
+ response.stream.close
+ end
+
+ raise 'An exception occurred...'
+ end
+
+ def exception_in_exception_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+ response.stream.on_error do
+ raise 'We need to go deeper.'
+ end
+ response.stream.write params[:widget][:didnt_check_for_nil]
+ end
end
tests TestController
@@ -66,6 +89,21 @@ module ActionController
TestResponse.new
end
+ def assert_stream_closed
+ assert response.stream.closed?, 'stream should be closed'
+ end
+
+ def capture_log_output
+ output = StringIO.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
+
+ begin
+ yield output
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
def test_set_response!
@controller.set_response!(@request)
assert_kind_of(Live::Response, @controller.response)
@@ -119,7 +157,43 @@ module ActionController
def test_render_text
get :render_text
assert_equal 'zomg', response.body
- assert response.stream.closed?, 'stream should be closed'
+ assert_stream_closed
+ end
+
+ def test_exception_handling_html
+ capture_log_output do |output|
+ get :exception_in_view
+ assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_handling_plain_text
+ capture_log_output do |output|
+ get :exception_in_view, format: :json
+ assert_equal '', response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_callback
+ capture_log_output do |output|
+ get :exception_with_callback, format: 'text/event-stream'
+ assert_equal %(data: "500 Internal Server Error"\n\n), response.body
+ assert_match 'An exception occurred...', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exceptions_raised_handling_exceptions
+ capture_log_output do |output|
+ get :exception_in_exception_callback, format: 'text/event-stream'
+ assert_equal '', response.body
+ assert_match 'We need to go deeper', output.rewind && output.read
+ assert_stream_closed
+ end
end
def test_stale_without_etag
diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb
index 2f1aa22208..9e5022c9f4 100644
--- a/actionpack/test/controller/new_base/render_partial_test.rb
+++ b/actionpack/test/controller/new_base/render_partial_test.rb
@@ -5,14 +5,14 @@ module RenderPartial
class BasicController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new(
- "render_partial/basic/_basic.html.erb" => "BasicPartial!",
- "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
- "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
- "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
- "render_partial/basic/_final.json.erb" => "{ final: json }",
- "render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
- "render_partial/basic/_overriden.html.erb" => "ParentPartial!",
- "render_partial/child/_overriden.html.erb" => "OverridenPartial!"
+ "render_partial/basic/_basic.html.erb" => "BasicPartial!",
+ "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
+ "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
+ "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
+ "render_partial/basic/_final.json.erb" => "{ final: json }",
+ "render_partial/basic/overridden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overridden' %><%= @test_unchanged %>",
+ "render_partial/basic/_overridden.html.erb" => "ParentPartial!",
+ "render_partial/child/_overridden.html.erb" => "OverriddenPartial!"
)]
def html_with_json_inside_json
@@ -24,7 +24,7 @@ module RenderPartial
render :action => "basic"
end
- def overriden
+ def overridden
@test_unchanged = 'hello'
end
end
@@ -55,8 +55,8 @@ module RenderPartial
end
test "partial from child controller gets picked" do
- get :overriden
- assert_response("goodbyeOverridenPartial!goodbye")
+ get :overridden
+ assert_response("goodbyeOverriddenPartial!goodbye")
end
end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index cc7f12ac6d..5635e16234 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -7,10 +7,10 @@ module Render
"render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>",
"render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>",
"render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>",
- "render/blank_render/overriden_with_own_view_paths_appended.html.erb" => "parent content",
- "render/blank_render/overriden_with_own_view_paths_prepended.html.erb" => "parent content",
- "render/blank_render/overriden.html.erb" => "parent content",
- "render/child_render/overriden.html.erb" => "child content"
+ "render/blank_render/overridden_with_own_view_paths_appended.html.erb" => "parent content",
+ "render/blank_render/overridden_with_own_view_paths_prepended.html.erb" => "parent content",
+ "render/blank_render/overridden.html.erb" => "parent content",
+ "render/child_render/overridden.html.erb" => "child content"
)]
def index
@@ -25,13 +25,13 @@ module Render
render :action => "access_action_name"
end
- def overriden_with_own_view_paths_appended
+ def overridden_with_own_view_paths_appended
end
- def overriden_with_own_view_paths_prepended
+ def overridden_with_own_view_paths_prepended
end
- def overriden
+ def overridden
end
private
@@ -49,8 +49,8 @@ module Render
end
class ChildRenderController < BlankRenderController
- append_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_appended.html.erb" => "child content")
- prepend_view_path ActionView::FixtureResolver.new("render/child_render/overriden_with_own_view_paths_prepended.html.erb" => "child content")
+ append_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_appended.html.erb" => "child content")
+ prepend_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_prepended.html.erb" => "child content")
end
class RenderTest < Rack::TestCase
@@ -114,17 +114,17 @@ module Render
class TestViewInheritance < Rack::TestCase
test "Template from child controller gets picked over parent one" do
- get "/render/child_render/overriden"
+ get "/render/child_render/overridden"
assert_body "child content"
end
test "Template from child controller with custom view_paths prepended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_prepended"
+ get "/render/child_render/overridden_with_own_view_paths_prepended"
assert_body "child content"
end
test "Template from child controller with custom view_paths appended gets picked over parent one" do
- get "/render/child_render/overriden_with_own_view_paths_appended"
+ get "/render/child_render/overridden_with_own_view_paths_appended"
assert_body "child content"
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 93e94f0f48..f735564305 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -715,17 +715,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def setup_request_method_routes_for(method)
rs.draw do
- match '/match' => 'books#get', :via => :get
- match '/match' => 'books#post', :via => :post
- match '/match' => 'books#put', :via => :put
- match '/match' => 'books#patch', :via => :patch
- match '/match' => 'books#delete', :via => :delete
+ match '/match' => "books##{method}", :via => method.to_sym
end
end
%w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
- setup_request_method_routes_for(request_method)
+ setup_request_method_routes_for(request_method.downcase)
params = rs.recognize_path("/match", :method => request_method)
assert_equal request_method.downcase, params[:action]
end
@@ -908,12 +904,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/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 888791b874..69bf4b7720 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -47,7 +47,7 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenController < ShowExceptionsController
+ class ShowExceptionsOverriddenController < ShowExceptionsController
private
def show_detailed_exceptions?
@@ -55,15 +55,15 @@ module ShowExceptions
end
end
- class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
+ class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
test 'show error page' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '0'}
assert_equal "500 error fixture\n", body
end
test 'show diagnostics message' do
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get '/', {'detailed' => '1'}
assert_match(/boom/, body)
end
@@ -71,7 +71,7 @@ module ShowExceptions
class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
def test_render_json_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/json'
assert_response :internal_server_error
assert_equal 'application/json', response.content_type.to_s
@@ -79,7 +79,7 @@ module ShowExceptions
end
def test_render_xml_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
assert_response :internal_server_error
assert_equal 'application/xml', response.content_type.to_s
@@ -87,7 +87,7 @@ module ShowExceptions
end
def test_render_fallback_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
assert_response :internal_server_error
assert_equal 'text/html', response.content_type.to_s
@@ -96,7 +96,7 @@ module ShowExceptions
class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest
def test_render_failsafe_exception
- @app = ShowExceptionsOverridenController.action(:boom)
+ @app = ShowExceptionsOverriddenController.action(:boom)
@exceptions_app = @app.instance_variable_get(:@exceptions_app)
@app.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 5ada5a7603..91ac13e7c6 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,6 +1,14 @@
require 'abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
require 'active_support/key_generator'
+require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -67,11 +75,21 @@ 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
end
+ def get_encrypted_cookie
+ cookies.encrypted[:foo]
+ head :ok
+ end
+
def set_invalid_encrypted_cookie
cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf'
head :ok
@@ -330,12 +348,17 @@ class CookiesTest < ActionController::TestCase
assert response.headers["Set-Cookie"] =~ /user_name=david/
end
- def test_permanent_cookie
+ def test_set_permanent_cookie
get :set_permanent_cookie
assert_match(/Jamie/, @response.headers["Set-Cookie"])
assert_match(%r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"])
end
+ def test_read_permanent_cookie
+ get :set_permanent_cookie
+ assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
+ end
+
def test_signed_cookie
get :set_signed_cookie
assert_equal 45, @controller.send(:cookies).signed[:user_id]
@@ -394,33 +417,148 @@ class CookiesTest < ActionController::TestCase
def test_raises_argument_error_if_missing_secret
assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
get :set_signed_cookie
}
assert_raise(ArgumentError, ''.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("")
get :set_signed_cookie
}
end
def test_raises_argument_error_if_secret_is_probably_insecure
assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
get :set_signed_cookie
}
assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
get :set_signed_cookie
}
assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789")
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
get :set_signed_cookie
}
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_signed_or_encrypted_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 :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_encrypted_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 :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_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 :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_encrypted_uses_encrypted_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 :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_encrypted_uses_upgrade_legacy_encrypted_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 :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_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"
+
+ 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_read_and_transparently_encrypted_by_encrypted_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"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @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_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "foo=baz"
+ get :get_encrypted_cookie
+
+ assert_equal nil, @controller.send(:cookies).encrypted[:foo]
+ assert_equal nil, @response.cookies["foo"]
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
@@ -669,3 +807,5 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index e2a9ba782d..6a2eb7da9f 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -75,7 +75,7 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, Mime::Type.parse(accept)
end
- test "parse arbitarry media type parameters" do
+ test "parse arbitrary media type parameters" do
accept = 'multipart/form-data; boundary="simple boundary"'
expect = [Mime::MULTIPART_FORM]
assert_equal expect, Mime::Type.parse(accept)
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/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2bf7056ff7..29703dd5b1 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1102,6 +1102,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#index', @response.body
end
+ def test_scope_with_format_option
+ draw do
+ get "direct/index", as: :no_format_direct, format: false
+
+ scope format: false do
+ get "scoped/index", as: :no_format_scoped
+ end
+ end
+
+ assert_equal "/direct/index", no_format_direct_path
+ assert_equal "/direct/index?format=html", no_format_direct_path(format: "html")
+
+ assert_equal "/scoped/index", no_format_scoped_path
+ assert_equal "/scoped/index?format=html", no_format_scoped_path(format: "html")
+
+ get '/scoped/index'
+ assert_equal "scoped#index", @response.body
+
+ get '/scoped/index.html'
+ assert_equal "Not Found", @response.body
+ end
+
def test_index
draw do
get '/info' => 'projects#info', :as => 'info'
@@ -1112,6 +1134,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#info', @response.body
end
+ def test_match_with_many_paths_containing_a_slash
+ draw do
+ get 'get/first', 'get/second', 'get/third', :to => 'get#show'
+ end
+
+ get '/get/first'
+ assert_equal 'get#show', @response.body
+
+ get '/get/second'
+ assert_equal 'get#show', @response.body
+
+ get '/get/third'
+ assert_equal 'get#show', @response.body
+ end
+
def test_match_shorthand_with_no_scope
draw do
get 'account/overview'
@@ -1134,6 +1171,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'account#shorthand', @response.body
end
+ def test_match_shorthand_with_multiple_paths_inside_namespace
+ draw do
+ namespace :proposals do
+ put 'activate', 'inactivate'
+ end
+ end
+
+ put '/proposals/activate'
+ assert_equal 'proposals#activate', @response.body
+
+ put '/proposals/inactivate'
+ assert_equal 'proposals#inactivate', @response.body
+ end
+
def test_match_shorthand_inside_namespace_with_controller
draw do
namespace :api do
@@ -2577,22 +2628,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 +2639,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 +2689,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/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index d8bf22dec8..e99ff46edf 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -1,12 +1,11 @@
require 'abstract_unit'
require 'stringio'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
- Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret)
+ Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
diff --git a/actionpack/test/fixtures/public/images/rails.png b/actionpack/test/fixtures/public/images/rails.png
deleted file mode 100644
index b8441f182e..0000000000
--- a/actionpack/test/fixtures/public/images/rails.png
+++ /dev/null
Binary files differ
diff --git a/actionpack/test/fixtures/test/change_priorty.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb
index 5618977d05..5618977d05 100644
--- a/actionpack/test/fixtures/test/change_priorty.html.erb
+++ b/actionpack/test/fixtures/test/change_priority.html.erb
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index dff0b8bdc2..1ff320224d 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -361,6 +361,16 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, file_field("user", "avatar")
end
+ def test_file_field_with_multiple_behavior
+ expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true)
+ end
+
+ def test_file_field_with_multiple_behavior_and_explicit_name
+ expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
+ assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom")
+ end
+
def test_hidden_field
assert_dom_equal(
'<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 29d63d9653..94ae8549f7 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -1095,12 +1095,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_priority_zones_as_regexp
@firm = Firm.new("D")
- priority_zones = /A|D/
@fake_timezones.each_with_index do |tz, i|
- priority_zones.stubs(:===).with(tz).returns(i.zero? || i == 3)
+ tz.stubs(:=~).returns(i.zero? || i == 3)
end
- html = time_zone_select("firm", "time_zone", priority_zones)
+ html = time_zone_select("firm", "time_zone", /A|D/)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"D\" selected=\"selected\">D</option>" +
@@ -1112,11 +1111,32 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
+ def test_time_zone_select_with_priority_zones_as_regexp_using_grep_finds_no_zones
+ @firm = Firm.new("D")
+
+ priority_zones = /A|D/
+ @fake_timezones.each do |tz|
+ priority_zones.stubs(:===).with(tz).raises(Exception)
+ end
+
+ html = time_zone_select("firm", "time_zone", priority_zones)
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
def test_time_zone_select_with_default_time_zone_and_nil_value
@firm = Firm.new()
@firm.time_zone = nil
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\" selected=\"selected\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -1127,16 +1147,17 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_select_with_default_time_zone_and_value
- @firm = Firm.new('D')
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
+ @firm = Firm.new('D')
+
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
end
def test_options_for_select_with_element_attributes
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 8111e58527..2237d747be 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -61,7 +61,7 @@ module RenderTestCases
def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names
@view.lookup_context.formats = [:html]
- assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priorty")
+ assert_equal "\nHTML Template, but JSON partial", @view.render(:template => "test/change_priority")
end
def test_render_template_with_a_missing_partial_of_another_format
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index e359f47975..9b4c419807 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -538,6 +538,22 @@ class UrlHelperTest < ActiveSupport::TestCase
assert mail_to("david@loudthinking.com").html_safe?
end
+ def test_mail_to_with_block
+ assert_dom_equal %{<a href="mailto:me@example.com"><span>Email me</span></a>},
+ mail_to('me@example.com') { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_with_block_and_options
+ assert_dom_equal %{<a class="special" href="mailto:me@example.com?cc=ccaddress%40example.com"><span>Email me</span></a>},
+ mail_to('me@example.com', cc: "ccaddress@example.com", class: "special") { content_tag(:span, 'Email me') }
+ end
+
+ def test_mail_to_does_not_modify_html_options_hash
+ options = { class: 'special' }
+ mail_to 'me@example.com', 'ME!', options
+ assert_equal({ class: 'special' }, options)
+ end
+
def protect_against_forgery?
self.request_forgery
end
@@ -596,7 +612,7 @@ class UrlHelperControllerTest < ActionController::TestCase
render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>"
end
- def show_overriden_url_for
+ def show_overridden_url_for
render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>"
end
@@ -633,8 +649,8 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
- def test_overriden_url_for_shows_only_path
- get :show_overriden_url_for
+ def test_overridden_url_for_shows_only_path
+ get :show_overridden_url_for
assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
end
@@ -684,7 +700,7 @@ class UrlHelperControllerTest < ActionController::TestCase
assert_equal 'ok', @response.body
end
- def test_url_helper_can_be_overriden
+ def test_url_helper_can_be_overridden
get :override_url_helper
assert_equal '/url_helper_controller_test/url_helper/override_url_helper/override', @response.body
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 62d684fd0b..9aee47bd52 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,21 @@
## Rails 4.0.0 (unreleased) ##
+* Add `ActiveModel::Errors#full_messages_for`, to return all the error messages
+ for a given attribute.
+
+ class Person
+ include ActiveModel::Validations
+
+ attr_reader :name, :email
+ validates_presence_of :name, :email
+ end
+
+ person = Person.new
+ person.valid? # => false
+ person.errors.full_messages_for(:name) # => ["Name can't be blank"]
+
+ *Volodymyr Shatsky*
+
* Added a method so that validations can be easily cleared on a model.
For example:
@@ -91,7 +107,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*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 1b1fe2fa2b..a66225319d 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -2,7 +2,7 @@
Active Model provides a known set of interfaces for usage in model classes.
They allow for Action Pack helpers to interact with non-Active Record models,
-for example. Active Model also helps building custom ORMs for use outside of
+for example. Active Model also helps with building custom ORMs for use outside of
the Rails framework.
Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e67cd2285..789bdd74bf 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -46,7 +46,7 @@ module ActiveModel
#
# A newly instantiated object is unchanged:
#
- # person = Person.find_by_name('Uncle Bob')
+ # person = Person.find_by(name: 'Uncle Bob')
# person.changed? # => false
#
# Change the name:
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 963e52bff3..b60458b3c6 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -12,7 +12,6 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# # Required dependency for ActiveModel::Errors
# extend ActiveModel::Naming
#
@@ -40,7 +39,6 @@ module ActiveModel
# def Person.lookup_ancestors
# [self]
# end
- #
# end
#
# The last three methods are required in your object for Errors to be
@@ -352,6 +350,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..de8a641924 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -30,21 +30,26 @@ module ActiveModel
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
- # user.save # => false, password required
+ # user.save # => false, password required
# user.password = 'mUc3m00RsqyRe'
- # user.save # => false, confirmation doesn't match
+ # user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
- # user.save # => true
- # user.authenticate('notright') # => false
- # user.authenticate('mUc3m00RsqyRe') # => user
- # User.find_by_name('david').try(:authenticate, 'notright') # => false
- # User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user
+ # user.save # => true
+ # user.authenticate('notright') # => false
+ # user.authenticate('mUc3m00RsqyRe') # => user
+ # User.find_by(name: 'david').try(:authenticate, 'notright') # => false
+ # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# 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 72e3cf310a..714cc95b9a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -376,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/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 51dcfc37d8..80ff97d03b 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -54,7 +54,7 @@ class ErrorsTest < ActiveModel::TestCase
assert errors.has_key?(:foo), 'errors should have key :foo'
end
- test "should be able to clear the errors" do
+ test "clear errors" do
person = Person.new
person.validate!
@@ -63,7 +63,7 @@ class ErrorsTest < ActiveModel::TestCase
assert person.errors.empty?
end
- test "get returns the error by the provided key" do
+ test "get returns the errors for the provided key" do
errors = ActiveModel::Errors.new(self)
errors[:foo] = "omg"
@@ -93,21 +93,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal [:foo, :baz], errors.keys
end
- test "as_json returns a json formatted representation of the errors hash" do
- person = Person.new
- person.validate!
-
- assert_equal({ name: ["can not be nil"] }, person.errors.as_json)
- end
-
- test "as_json with :full_messages option" do
- person = Person.new
- person.validate!
-
- assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true))
- end
-
- test "should return true if no errors" do
+ test "detecting whether there are errors with empty?, blank?, include?" do
person = Person.new
person.errors[:foo]
assert person.errors.empty?
@@ -115,139 +101,154 @@ class ErrorsTest < ActiveModel::TestCase
assert !person.errors.include?(:foo)
end
- test "method validate! should work" do
+ test "adding errors using conditionals with Person#validate!" do
person = Person.new
person.validate!
assert_equal ["name can not be nil"], person.errors.full_messages
assert_equal ["can not be nil"], person.errors[:name]
end
- test 'should be able to assign error' do
+ test "assign error" do
person = Person.new
person.errors[:name] = 'should not be nil'
assert_equal ["should not be nil"], person.errors[:name]
end
- test 'should be able to add an error on an attribute' do
+ test "add an error message on a specific attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_equal ["can not be blank"], person.errors[:name]
end
- test "should be able to add an error with a symbol" do
+ test "add an error with a symbol" do
person = Person.new
person.errors.add(:name, :blank)
message = person.errors.generate_message(:name, :blank)
assert_equal [message], person.errors[:name]
end
- test "should be able to add an error with a proc" do
+ test "add an error with a proc" do
person = Person.new
message = Proc.new { "can not be blank" }
person.errors.add(:name, message)
assert_equal ["can not be blank"], person.errors[:name]
end
- test "added? should be true if that error was added" do
+ test "added? detects if a specific error was added to the object" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert person.errors.added?(:name, "can not be blank")
end
- test "added? should handle when message is a symbol" do
+ test "added? handles symbol message" do
person = Person.new
person.errors.add(:name, :blank)
assert person.errors.added?(:name, :blank)
end
- test "added? should handle when message is a proc" do
+ test "added? handles proc messages" do
person = Person.new
message = Proc.new { "can not be blank" }
person.errors.add(:name, message)
assert person.errors.added?(:name, message)
end
- test "added? should default message to :invalid" do
+ test "added? defaults message to :invalid" do
person = Person.new
person.errors.add(:name)
assert person.errors.added?(:name)
end
- test "added? should be true when several errors are present, and we ask for one of them" do
+ test "added? matches the given message when several errors are present for the same attribute" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "is invalid")
assert person.errors.added?(:name, "can not be blank")
end
- test "added? should be false if no errors are present" do
+ test "added? returns false when no errors are present" do
person = Person.new
assert !person.errors.added?(:name)
end
- test "added? should be false when an error is present, but we check for another error" do
+ test "added? returns false when checking a nonexisting error and other errors are present for the given attribute" do
person = Person.new
person.errors.add(:name, "is invalid")
assert !person.errors.added?(:name, "can not be blank")
end
- test 'should respond to size' do
+ test "size calculates the number of error messages" do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_equal 1, person.errors.size
end
- test 'to_a should return an array' do
+ test "to_a returns the list of errors with complete messages containing the attribute names" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
end
- test 'to_hash should return a hash' do
+ test "to_hash returns the error messages hash" do
person = Person.new
person.errors.add(:name, "can not be blank")
- assert_instance_of ::Hash, person.errors.to_hash
+ assert_equal({ name: ["can not be blank"] }, person.errors.to_hash)
end
- test 'full_messages should return an array of error messages, with the attribute name included' do
+ test "full_messages creates a list of error messages with the attribute name included" do
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
end
- test 'full_message should return the given message if attribute equals :base' do
+ test "full_messages_for contains all the error messages for the given attribute" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages_for(:name)
+ end
+
+ test "full_messages_for does not contain error messages from other attributes" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:email, "can not be blank")
+ assert_equal ["name can not be blank"], person.errors.full_messages_for(:name)
+ end
+
+ test "full_messages_for returns an empty list in case there are no errors for the given attribute" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal [], person.errors.full_messages_for(:email)
+ end
+
+ test "full_message returns the given message when attribute is :base" do
person = Person.new
assert_equal "press the button", person.errors.full_message(:base, "press the button")
end
- test 'full_message should return the given message with the attribute name included' do
+ test "full_message returns the given message with the attribute name included" do
person = Person.new
assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
+ assert_equal "name_test can not be blank", person.errors.full_message(:name_test, "can not be blank")
end
- test 'should return a JSON hash representation of the errors' do
+ test "as_json creates a json formatted representation of the errors hash" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- person.errors.add(:email, "is invalid")
- hash = person.errors.as_json
- assert_equal ["can not be blank", "can not be nil"], hash[:name]
- assert_equal ["is invalid"], hash[:email]
+ person.validate!
+
+ assert_equal({ name: ["can not be nil"] }, person.errors.as_json)
end
- test 'should return a JSON hash representation of the errors with full messages' do
+ test "as_json with :full_messages option creates a json formatted representation of the errors containing complete messages" do
person = Person.new
- person.errors.add(:name, "can not be blank")
- person.errors.add(:name, "can not be nil")
- person.errors.add(:email, "is invalid")
- hash = person.errors.as_json(:full_messages => true)
- assert_equal ["name can not be blank", "name can not be nil"], hash[:name]
- assert_equal ["email is invalid"], hash[:email]
+ person.validate!
+
+ assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true))
end
- test "generate_message should work without i18n_scope" do
+ test "generate_message works without i18n_scope" do
person = Person.new
assert !Person.respond_to?(:i18n_scope)
assert_nothing_raised {
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 99a9c1fe33..04ecf8a31b 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -50,14 +50,10 @@ class XmlSerializationTest < ActiveModel::TestCase
customer.name = "John"
@contact.preferences = customer
@contact.address = Address.new
- @contact.address.street = "123 Lane"
@contact.address.city = "Springfield"
- @contact.address.state = "CA"
- @contact.address.zip = 11111
@contact.address.apt_number = 35
@contact.friends = [Contact.new, Contact.new]
@related_contact = SerializableContact.new
- @related_contact.name = "related"
@contact.contact = @related_contact
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 3e368407f2..58f6153e96 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,10 +1,121 @@
## Rails 4.0.0 (unreleased) ##
-* If inverse_of is true on an association, then when one calls +find()+ on
- the association, ActiveRecord will first look through the in-memory objects
+* `default_scopes?` is deprecated. Check for `default_scopes.empty?` instead.
+
+ *Agis Anastasopoulos*
+
+* Default values for PostgreSQL bigint types now get parsed and dumped to the
+ schema correctly.
+
+ *Erik Peterson*
+
+* Fix associations with `:inverse_of` option when building association
+ with a block. Inside the block the parent object was different then
+ after the block.
+
+ Example:
+
+ parent.association.build do |child|
+ child.parent.equal?(parent) # false
+ end
+
+ # vs
+
+ child = parent.association.build
+ child.parent.equal?(parent) # true
+
+ *Michal Cichra*
+
+* `has_many` using `:through` now obeys the order clause mentioned in
+ through association. Fixes #10016.
+
+ *Neeraj Singh*
+
+* `belongs_to :touch` behavior now touches old association when
+ transitioning to new association.
+
+ class Passenger < ActiveRecord::Base
+ belongs_to :car, touch: true
+ end
+
+ car_1 = Car.create
+ car_2 = Car.create
+
+ passenger = Passenger.create car: car_1
+
+ passenger.car = car_2
+ passenger.save
+
+ Previously only car_2 would be touched. Now both car_1 and car_2
+ will be touched.
+
+ *Adam Gamble*
+
+* Extract and deprecate Firebird / Sqlserver / Oracle database tasks, because
+ These tasks should be supported by 3rd-party adapter.
+
+ *kennyj*
+
+* Allow `ActiveRecord::Base.connection_handler` to have thread affinity and be
+ settable, this effectively allows Active Record 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 now supported by Active Record. 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 add support for MySQL index algorithms (`COPY`, `INPLACE`,
+ `DEFAULT`) via the `:algorithm` 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.
+ is not found. This is accomplished by calling `find_by_scan` in
+ collection associations whenever `options[:inverse_of]` is not nil.
Fixes #9470.
@@ -91,11 +202,11 @@
*Yves Senn*
-* Fix quoting for sqlite migrations using copy_table_contents() with binary
+* 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
+ the column was not being passed to `quote` so the data was not quoted
correctly.
*Matthew M. Boedicker*
@@ -268,7 +379,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*
@@ -498,7 +609,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
@@ -521,7 +632,7 @@
*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*
@@ -685,7 +796,7 @@
*Yves Senn*
-* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`.
+* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`.
Fixes #5523.
*Gary S. Weaver*
@@ -809,14 +920,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*
@@ -1448,7 +1559,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:
@@ -1716,7 +1827,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:
@@ -1744,13 +1855,13 @@
add_index(:accounts, :code, where: 'active')
- Generates
+ generates
CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
*Marcelo Silveira*
-* Implemented ActiveRecord::Relation#none method.
+* Implemented `ActiveRecord::Relation#none` method.
The `none` method returns a chainable relation with zero records
(an instance of the NullRelation class).
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index ed1e171d58..822e460918 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -190,7 +190,7 @@ The latest version of Active Record can be installed with RubyGems:
% [sudo] gem install activerecord
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/activerecord
@@ -204,7 +204,7 @@ Active Record is released under the MIT license:
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
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.rb b/activerecord/lib/active_record.rb
index c33f03f13f..3bfc6772b2 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -35,8 +35,8 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
autoload :Core
- autoload :CounterCache
autoload :ConnectionHandling
+ autoload :CounterCache
autoload :DynamicMatchers
autoload :Explain
autoload :Inheritance
@@ -69,8 +69,8 @@ module ActiveRecord
autoload :Aggregations
autoload :Associations
- autoload :AttributeMethods
autoload :AttributeAssignment
+ autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :Relation
@@ -143,6 +143,10 @@ module ActiveRecord
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
autoload :PostgreSQLDatabaseTasks,
'active_record/tasks/postgresql_database_tasks'
+
+ autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
+ autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
+ autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
end
autoload :TestCase
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index eb08f72286..3c92e379f1 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -988,7 +988,7 @@ module ActiveRecord
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
#
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
# to be removed from the database.
#
@@ -1115,11 +1115,11 @@ module ActiveRecord
# similar callbacks may affect the :dependent behavior, and the
# :dependent behavior may affect other callbacks.
#
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 868095f068..729ef8c55a 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
@@ -217,7 +217,8 @@ module ActiveRecord
reflection.inverse_of
end
- # Is this association invertible? Can be redefined by subclasses.
+ # Returns true if inverse association on the given record needs to be set.
+ # This method is redefined by subclasses.
def invertible_for?(record)
inverse_reflection_for(record)
end
@@ -235,6 +236,7 @@ module ActiveRecord
skip_assign = [reflection.foreign_key, reflection.type].compact
attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes)
+ set_inverse_instance(record)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a9525436fb..aa5551fe0c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -101,6 +101,7 @@ module ActiveRecord
scope.includes! item.includes_values
scope.where_values += item.where_values
+ scope.order_values |= item.order_values
end
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 fbcb21118d..3ba6a71366 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder
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
@@ -66,8 +66,19 @@ module ActiveRecord::Associations::Builder
def add_touch_callbacks(reflection)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_touch_after_save_or_destroy_for_#{name}
- record = #{name}
+ foreign_key_field = #{reflection.foreign_key.inspect}
+ old_foreign_id = attribute_was(foreign_key_field)
+
+ if old_foreign_id
+ reflection_klass = #{reflection.klass}
+ old_record = reflection_klass.find_by(reflection_klass.primary_key => old_foreign_id)
+
+ if old_record
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
+ end
+ end
+ record = #{name}
unless record.nil? || record.new_record?
record.touch #{options[:touch].inspect if options[:touch] != true}
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 51bcac61c7..2a00ac1386 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -79,8 +79,20 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql] || options[:inverse_of]
+ if options[:finder_sql]
find_by_scan(*args)
+ elsif options[:inverse_of]
+ args = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
+
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args.size
+ scope.raise_record_not_found_exception!(args, result_size, args.size)
+ else
+ result
+ end
else
scope.find(*args)
end
@@ -318,7 +330,7 @@ module ActiveRecord
# 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?
@@ -465,7 +477,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?
@@ -506,7 +518,7 @@ module ActiveRecord
result = true
records.flatten.each do |record|
- raise_on_type_mismatch(record)
+ raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec) unless owner.new_record?
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c2add32aa6..8a5b312862 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -228,6 +228,7 @@ module ActiveRecord
def build(attributes = {}, &block)
@association.build(attributes, &block)
end
+ alias_method :new, :build
# Returns a new object of the collection type that has been instantiated with
# attributes, linked to this object and that has already been saved (if it
@@ -832,8 +833,6 @@ module ActiveRecord
@association.include?(record)
end
- alias_method :new, :build
-
def proxy_association
@association
end
@@ -848,10 +847,8 @@ module ActiveRecord
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- association = @association
-
- @association.scope.extending! do
- define_method(:proxy_association) { association }
+ @association.scope.tap do |scope|
+ scope.proxy_association = @association
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 b7b4d7e3ae..29fae809da 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -22,7 +22,7 @@ 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
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/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index f40368cfeb..d3ceecb090 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -5,10 +5,10 @@ module ActiveRecord
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
def initialize(base, associations, joins)
- @active_record = base
+ @base_klass = base
@table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@@ -54,7 +54,7 @@ module ActiveRecord
parent
}.uniq
- remove_duplicate_results!(active_record, records, @associations)
+ remove_duplicate_results!(base_klass, records, @associations)
records
end
@@ -108,8 +108,8 @@ module ActiveRecord
parent ||= join_parts.last
case associations
when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ reflection = parent.reflections[associations.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
unless join_association = find_join_association(reflection, parent)
@reflections << reflection
join_association = build_join_association(reflection, parent)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0d3b4dbab1..7fa0844a5e 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -59,10 +59,10 @@ module ActiveRecord
end
end
- def join_to(relation)
+ def join_to(manager)
tables = @tables.dup
foreign_table = parent_table
- foreign_klass = parent.active_record
+ foreign_klass = parent.base_klass
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -75,7 +75,7 @@ module ActiveRecord
foreign_key = reflection.foreign_key
when :has_and_belongs_to_many
# Join the join table first...
- relation.from(join(
+ manager.from(join(
table,
table[reflection.foreign_key].
eq(foreign_table[reflection.active_record_primary_key])
@@ -109,13 +109,13 @@ module ActiveRecord
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
end
- relation.from(join(table, constraint))
+ manager.from(join(table, constraint))
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, reflection.klass
end
- relation
+ manager
end
def build_constraint(reflection, table, key, foreign_table, foreign_key)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 3920e84976..a7dacdbfd6 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class JoinBase < JoinPart # :nodoc:
def ==(other)
other.class == self.class &&
- other.active_record == active_record
+ other.base_klass == base_klass
end
def aliased_prefix
@@ -16,7 +16,7 @@ module ActiveRecord
end
def aliased_table_name
- active_record.table_name
+ base_klass.table_name
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 5604687b57..9e33ccb59f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -11,12 +11,12 @@ module ActiveRecord
# The Active Record class which this join part is associated 'about'; for a JoinBase
# this is the actual base model, for a JoinAssociation this is the target model of the
# association.
- attr_reader :active_record
+ attr_reader :base_klass
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
- def initialize(active_record)
- @active_record = active_record
+ def initialize(base_klass)
+ @base_klass = base_klass
@cached_record = {}
@column_names_with_alias = nil
end
@@ -70,7 +70,7 @@ module ActiveRecord
end
def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
end
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 38bc7ce7da..157b627ad5 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -5,9 +5,13 @@ module ActiveRecord
include ThroughAssociation
def associated_records_by_owner
- super.each do |owner, records|
- records.uniq! if reflection_scope.distinct_value
+ records_by_owner = super
+
+ if reflection_scope.distinct_value
+ records_by_owner.each_value { |records| records.uniq! }
end
+
+ records_by_owner
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 43520142bf..35f29b37a2 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,7 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(
+ scope.merge!(
reflection.klass.all.with_default_scope.
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
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 aa92343f76..22405c5d74 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -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/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 55542262b0..44323ce9db 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -62,14 +62,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.author.id
- # Author.find_by_id(id).nil? # => false
+ # Author.find_by(id: id).nil? # => false
#
# post.save
# post.reload.author # => nil
#
# Now it _is_ removed from the database:
#
- # Author.find_by_id(id).nil? # => true
+ # Author.find_by(id: id).nil? # => true
#
# === One-to-many Example
#
@@ -113,14 +113,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.comments.last.id
- # Comment.find_by_id(id).nil? # => false
+ # Comment.find_by(id: id).nil? # => false
#
# post.save
# post.reload.comments.length # => 1
#
# Now it _is_ removed from the database:
#
- # Comment.find_by_id(id).nil? # => true
+ # Comment.find_by(id: id).nil? # => true
module AutosaveAssociation
extend ActiveSupport::Concern
@@ -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/base.rb b/activerecord/lib/active_record/base.rb
index e262401da6..b06add096f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -160,10 +160,10 @@ module ActiveRecord #:nodoc:
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
# by simple queries without turning to SQL. They work by appending the name of an attribute
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
- # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do
+ # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
# <tt>Person.find_by_user_name(user_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
@@ -172,7 +172,7 @@ module ActiveRecord #:nodoc:
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
- # Person.where(user_name: user_name, password: password).first
+ # Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f6cdc67b4d..d3d7396c91 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
@@ -24,19 +23,15 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
- begin
- obj = YAML.load(yaml)
-
- unless obj.is_a?(object_class) || obj.nil?
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
- end
- obj ||= object_class.new if object_class != Object
-
- obj
- rescue *RESCUE_ERRORS
- yaml
+ obj = YAML.load(yaml)
+
+ unless obj.is_a?(object_class) || obj.nil?
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
end
+ obj ||= object_class.new if object_class != Object
+
+ obj
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 902dbd148e..566550cbe2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -8,41 +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 primary_key?
- type.to_sym == :primary_key
- 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?
- column_options[:column] = self
- add_column_options!(column_sql, column_options) unless primary_key?
- column_sql
+ primary_key || type.to_sym == :primary_key
end
-
- private
-
- def add_column_options!(sql, options)
- base.add_column_options!(sql, options)
- end
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -68,19 +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 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+.
@@ -233,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!
@@ -278,28 +258,31 @@ module ActiveRecord
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
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 } * ', '
- end
+ 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
- private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ 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
- def new_column_definition(base, name, type)
- definition = create_column_definition base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ private
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
def primary_key_column_name
@@ -308,7 +291,24 @@ module ActiveRecord
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 cd4409295f..9c0c4e3ef0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -171,14 +171,14 @@ module ActiveRecord
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition
+ 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
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
yield td if block_given?
@@ -187,11 +187,7 @@ 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
@@ -359,9 +355,9 @@ 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.
@@ -447,7 +443,7 @@ module ActiveRecord
#
# add_index(:suppliers, :name)
#
- # generates
+ # generates:
#
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
@@ -455,7 +451,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true)
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
@@ -463,7 +459,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
@@ -471,13 +467,13 @@ module ActiveRecord
#
# add_index(:accounts, :name, name: 'by_name', length: 10)
#
- # generates
+ # generates:
#
# CREATE INDEX by_name ON accounts(name(10))
#
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
#
- # generates
+ # generates:
#
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
@@ -487,7 +483,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
#
- # generates
+ # generates:
#
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
@@ -497,11 +493,30 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
#
- # generates
+ # 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.
+ # ====== Creating an index with a specific method
+ #
+ # add_index(:developers, :name, using: 'btree')
+ #
+ # generates:
+ #
+ # 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}"
@@ -685,6 +700,9 @@ 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.
@@ -749,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 options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ }
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
@@ -769,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
@@ -779,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 = {})
@@ -829,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_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7949bcb5ce..1138b10a1b 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,12 @@ module ActiveRecord
[]
end
+ # A list of index algorithms, to be filled by adapters that support them.
+ # MySQL and PostgreSQL have support for them right now.
+ def index_algorithms
+ {}
+ end
+
# 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..94d9efe521 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
@@ -61,6 +82,8 @@ module ActiveRecord
def extract_limit(sql_type)
case sql_type
+ when /^enum\((.+)\)/i
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
when /blob|text/i
case sql_type
when /tiny/i
@@ -77,8 +100,6 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
- when /^enum\((.+)\)/i
- $1.split(',').map{|enum| enum.strip.length - 2}.max
else
super
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/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a4b3a0c584..609ccc2ed2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -161,7 +161,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.blank?
+ return nil if value.empty?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -172,14 +172,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
dummy_time_string = "2000-01-01 #{string}"
@@ -192,7 +192,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value.is_a?(String) && value.blank?
+ if value.is_a?(String) && value.empty?
nil
else
TRUE_VALUES.include?(value)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 25b8aef617..530a27d099 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -4,7 +4,7 @@ gem 'mysql2', '~> 0.3.10'
require 'mysql2'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
@@ -63,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 7544c2a783..09ba2e0d4a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -16,9 +16,9 @@ class Mysql
end
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
- def mysql_connection(config) # :nodoc:
+ def mysql_connection(config)
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -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 43f991b362..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,14 +57,14 @@ module ActiveRecord
super
end
when Numeric
- if column.sql_type == 'money' || [:string, :text].include?(column.type)
+ 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/
@@ -90,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..d9b807bba4 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+.
@@ -120,12 +156,15 @@ module ActiveRecord
column_names = columns.values_at(*indkey).compact
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- 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]
+ unless column_names.empty?
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ 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)
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ end
end.compact
end
@@ -337,10 +376,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 +411,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 940de7e4f6..bf403c3ae0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,7 +16,7 @@ require 'pg'
require 'ipaddr'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
:client_encoding, :options, :application_name, :fallback_application_name,
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
@@ -24,7 +24,7 @@ module ActiveRecord
:requirepeer, :krbsrvname, :gsslib, :service]
# Establishes a connection to the database that's used by all Active Record objects
- def postgresql_connection(config) # :nodoc:
+ def postgresql_connection(config)
conn_params = config.symbolize_keys
conn_params.delete_if { |_, v| v.nil? }
@@ -80,7 +80,7 @@ module ActiveRecord
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
$1
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
$1
# Character types
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
@@ -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]
@@ -344,8 +351,8 @@ module ActiveRecord
private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
end
@@ -426,6 +433,10 @@ module ActiveRecord
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -627,19 +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
-
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
- else
- super
- end
- end
-
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
@@ -669,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.
@@ -713,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
@@ -900,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/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d3ffee3a8b..7d940fe1c9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,9 +6,9 @@ gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# sqlite3 adapter reuses sqlite_connection.
- def sqlite3_connection(config) # :nodoc:
+ def sqlite3_connection(config)
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
@@ -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
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index d6d998c7be..1e03414c29 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -15,15 +15,15 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
- # database: "path/to/dbfile"
+ # adapter: "sqlite",
+ # database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
- # "database" => "path/to/dbfile"
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
# )
#
# Or a URL:
@@ -79,7 +79,7 @@ module ActiveRecord
connection_handler.retrieve_connection(self)
end
- # Returns true if Active Record is connected.
+ # Returns +true+ if Active Record is connected.
def connected?
connection_handler.connected?(self)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index aa56219755..afa1766615 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -77,8 +77,17 @@ module ActiveRecord
mattr_accessor :disable_implicit_join_references, instance_writer: false
self.disable_implicit_join_references = false
- class_attribute :connection_handler, instance_writer: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ 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
@@ -176,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
@@ -246,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 = {}
@@ -336,6 +344,10 @@ module ActiveRecord
self.class.connection
end
+ def connection_handler
+ self.class.connection_handler
+ end
+
# Returns the contents of the record as a nicely formatted string.
def inspect
inspection = if @attributes
@@ -427,11 +439,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/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..45dc26f0ed 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -708,11 +708,18 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- included do
- setup :setup_fixtures
- teardown :teardown_fixtures
+ def before_setup
+ setup_fixtures
+ super
+ end
+
+ def after_teardown
+ super
+ teardown_fixtures
+ end
- class_attribute :fixture_path
+ included do
+ class_attribute :fixture_path, :instance_writer => false
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
@@ -752,7 +759,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
@@ -765,8 +772,7 @@ module ActiveRecord
def try_to_load_dependency(file_name)
require_dependency file_name
rescue LoadError => e
- # Let's hope the developer has included it himself
-
+ # Let's hope the developer has included it
# Let's warn in case this is a subdependency, otherwise
# subdependency error messages are totally cryptic
if ActiveRecord::Base.logger
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f54865c86e..8df76c7f5f 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -174,7 +174,7 @@ module ActiveRecord
if subclass_name.present? && subclass_name != self.name
subclass = subclass_name.safe_constantize
- unless subclasses.include?(subclass)
+ unless descendants.include?(subclass)
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 48c73d7781..2589b2f3da 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# <tt>resources :users</tt> route. Normally, +user_path+ will
# construct a path with the user object's 'id' in it:
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/1"
#
# You can override +to_param+ in your model to make +user_path+ construct
@@ -33,7 +33,7 @@ module ActiveRecord
# end
# end
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 27d37766b7..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+.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 602ab9e2f4..d607f49e2b 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -90,8 +90,9 @@ module ActiveRecord
# accepts_nested_attributes_for :posts
# end
#
- # You can now set or update attributes on an associated post model through
- # the attribute hash.
+ # You can now set or update attributes on the associated posts through
+ # an attribute hash for a member: include the key +:posts_attributes+
+ # with an array of hashes of post attributes as a value.
#
# For each hash that does _not_ have an <tt>id</tt> key a new record will
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
@@ -114,10 +115,10 @@ module ActiveRecord
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
+ # end
#
# params = { member: {
# name: 'joe', posts_attributes: [
@@ -134,19 +135,19 @@ module ActiveRecord
#
# Alternatively, :reject_if also accepts a symbol for using methods:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :new_record?
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
+ # end
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :reject_posts
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
- # end
- # end
+ # def reject_posts(attributed)
+ # attributed['title'].blank?
+ # end
+ # end
#
# If the hash contains an <tt>id</tt> key that matches an already
# associated record, the matching record will be modified:
@@ -183,6 +184,29 @@ module ActiveRecord
# member.save
# member.reload.posts.length # => 1
#
+ # Nested attributes for an associated collection can also be passed in
+ # the form of a hash of hashes instead of an array of hashes:
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: { first: { title: 'Foo' },
+ # second: { title: 'Bar' } })
+ #
+ # has the same effect as
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: [ { title: 'Foo' },
+ # { title: 'Bar' } ])
+ #
+ # The keys of the hash which is the value for +:posts_attributes+ are
+ # ignored in this case.
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # such keys, otherwise the hash will be wrapped in an array and
+ # interpreted as an attribute hash for a single post.
+ #
+ # Passing attributes for an associated collection in the form of a hash
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
+ # where there maybe no natural way to submit an array of hashes.
+ #
# === Saving
#
# All changes to models, including the destruction of those marked for
@@ -338,7 +362,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present?
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -428,7 +452,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
end
end
end
@@ -490,7 +514,7 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b25d0601cb..42cece3ad0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -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/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d92e268109..48febcbc43 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -166,7 +166,7 @@ db_namespace = namespace :db do
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => [:environment, :load_config] do
+ task :abort_if_pending_migrations => :environment do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -270,32 +270,11 @@ db_namespace = namespace :db do
end
namespace :structure do
- def set_firebird_env(config)
- ENV['ISC_USER'] = config['username'].to_s if config['username']
- ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
- end
-
- def firebird_db_string(config)
- FireRuby::Database.db_string_for(config.symbolize_keys)
- end
-
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- case current_config['adapter']
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when 'sqlserver'
- `smoscript -s #{current_config['host']} -d #{current_config['database']} -u #{current_config['username']} -p #{current_config['password']} -f #{filename} -A -U`
- when "firebird"
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -a #{db_string} > #{filename}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
if ActiveRecord::Base.connection.supports_migrations?
File.open(filename, "a") do |f|
@@ -307,23 +286,9 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case current_config['adapter']
- when 'sqlserver'
- `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}`
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- IO.read(filename).split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -i #{filename} #{db_string}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
- end
+ current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
end
task :load_if_sql => ['db:create', :environment] do
@@ -378,29 +343,11 @@ db_namespace = namespace :db do
# desc "Empty the test database"
task :purge => [:environment, :load_config] do
- abcs = ActiveRecord::Base.configurations
- case abcs['test']['adapter']
- when 'sqlserver'
- test = abcs.deep_dup['test']
- test_database = test['database']
- test['database'] = 'master'
- ActiveRecord::Base.establish_connection(test)
- ActiveRecord::Base.connection.recreate_database!(test_database)
- when "oci", "oracle"
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database!
- else
- ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
- end
+ ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => 'db:abort_if_pending_migrations' do
+ task :prepare do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -426,7 +373,7 @@ namespace :railties do
puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists."
end
- on_copy = Proc.new do |name, migration, old_path|
+ on_copy = Proc.new do |name, migration|
puts "Copied migration #{migration.basename} from #{name}"
end
@@ -436,5 +383,5 @@ namespace :railties do
end
end
-task 'test:prepare' => 'db:test:prepare'
+task 'test:prepare' => ['db:test:prepare', 'db:test:load_schema', 'db:abort_if_pending_migrations']
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0995750ecd..9403273db0 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -401,6 +401,16 @@ module ActiveRecord
# has_many :tags, through: :taggings
# end
#
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :post
+ # belongs_to :tag
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ #
+ # taggings_reflection = tags_reflection.source_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ #
def source_reflection
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
end
@@ -426,6 +436,17 @@ module ActiveRecord
# The chain is built by recursively calling #chain on the source reflection and the through
# reflection. The base case for the recursion is a normal association, which just returns
# [self] as its #chain.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.chain
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ #
def chain
@chain ||= begin
chain = source_reflection.chain + through_reflection.chain
@@ -496,9 +517,16 @@ module ActiveRecord
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- # Gets an array of possible <tt>:through</tt> source reflection names:
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
#
- # [:singularized, :pluralized]
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.source_reflection_names
+ # # => [:tag, :tags]
#
def source_reflection_names
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 037097d2dd..56462d355b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,14 +10,14 @@ module ActiveRecord
:extending]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :distinct, :create_with]
+ :reverse_order, :distinct, :create_with, :uniq]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :default_scoped
+ attr_accessor :default_scoped, :proxy_association
alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
@@ -603,7 +603,7 @@ module ActiveRecord
"\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. " \
@@ -612,7 +612,7 @@ module ActiveRecord
"\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" \
+ "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
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index be011b22af..64e1ff9a6a 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# puts values["Drake"]
# # => 43
#
- # drake = Family.find_by_last_name('Drake')
+ # drake = Family.find_by(last_name: 'Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# # => 43
@@ -135,7 +135,7 @@ module ActiveRecord
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
- # Person.uniq.pluck(:role)
+ # Person.pluck('DISTINCT role')
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
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/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 14520381c9..72e9272cd7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
# Finds the first record matching the specified conditions. There
- # is no implied ording so if order matters, you should specify it
+ # is no implied ordering so if order matters, you should specify it
# yourself.
#
# If no record is found, returns <tt>nil</tt>.
@@ -130,8 +130,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Returns +true+ if a record exists in the table that matches the +id+ or
- # conditions given, or +false+ otherwise. The argument can take six forms:
+ # Returns truthy if a record exists in the table that matches the +id+ or
+ # conditions given, or falsy otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -176,6 +176,28 @@ module ActiveRecord
false
end
+ # This method is called whenever no records are found with either a single
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
+ #
+ # The error message is different depending on whether a single id or
+ # multiple ids are provided. If multiple ids are provided, then the number
+ # of results obtained should be provided in the +result_size+ argument and
+ # the expected number of results should be provided in the +expected_size+
+ # argument.
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
+
+ if Array(ids).size == 1
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ else
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
+ end
+
+ raise RecordNotFound, error
+ end
+
protected
def find_with_associations
@@ -259,11 +281,7 @@ module ActiveRecord
relation.bind_values += [[column, id]]
record = relation.take
- unless record
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
- end
+ raise_record_not_found_exception!(id, 0, 1) unless record
record
end
@@ -286,12 +304,7 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
-
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
- raise RecordNotFound, error
+ raise_record_not_found_exception!(ids, result.size, expected_size)
end
end
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/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 9746b1c3c2..f108ab0fe0 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -9,11 +9,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- Thread.current["#{self}_current_scope"]
+ ScopeRegistry.value_for(:current_scope, base_class.to_s)
end
def current_scope=(scope) #:nodoc:
- Thread.current["#{self}_current_scope"] = scope
+ ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
end
end
@@ -24,5 +24,63 @@ module ActiveRecord
send("#{att}=", value) if respond_to?("#{att}=")
end
end
+
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
+ # for different classes. The registry is stored as a thread local, which is
+ # accessed through +ScopeRegistry.current+.
+ #
+ # This class allows you to store and get the scope values on different
+ # classes and different types of scopes. For example, if you are attempting
+ # to get the current_scope for the +Board+ model, then you would use the
+ # following code:
+ #
+ # registry = ActiveRecord::Scoping::ScopeRegistry.current
+ # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ #
+ # Now when you run:
+ #
+ # registry.value_for(:current_scope, "Board")
+ #
+ # You will obtain whatever was defined in +some_new_scope+. The +value_for+
+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+
+ # object, so the above example code can also be called as:
+ #
+ # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
+ # "Board", some_new_scope)
+ class ScopeRegistry # :nodoc:
+ class << self
+ delegate :value_for, :set_value_for, to: :current
+
+ def current
+ Thread.current["scope_registry"] ||= new
+ end
+ end
+
+ VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
+
+ def initialize
+ @registry = Hash.new { |hash, key| hash[key] = {} }
+ end
+
+ # Obtains the value for a given +scope_name+ and +variable_name+.
+ def value_for(scope_type, variable_name)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name]
+ end
+
+ # Sets the +value+ for a given +scope_type+ and +variable_name+.
+ def set_value_for(scope_type, variable_name, value)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name] = value
+ end
+
+ private
+
+ def raise_invalid_scope_type!(scope_type)
+ if !VALID_SCOPE_TYPES.include?(scope_type)
+ raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5bd481082e..d37d33d552 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,8 +5,17 @@ module ActiveRecord
included do
# Stores the default scope for the class.
- class_attribute :default_scopes, instance_writer: false
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false
+
self.default_scopes = []
+
+ def self.default_scopes?
+ ActiveSupport::Deprecation.warn(
+ "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
+ )
+
+ !!self.default_scopes
+ end
end
module ClassMethods
@@ -27,14 +36,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
@@ -119,11 +120,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- Thread.current["#{self}_ignore_default_scope"]
+ ScopeRegistry.value_for(:ignore_default_scope, self)
end
def ignore_default_scope=(ignore) # :nodoc:
- Thread.current["#{self}_ignore_default_scope"] = ignore
+ ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 12317601b6..da73bead32 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -160,13 +160,8 @@ module ActiveRecord
singleton_class.send(:define_method, name) do |*args|
if body.respond_to?(:call)
- scope = extension ? body.call(*args).extending(extension) : body.call(*args)
-
- if scope
- default_scoped = scope.default_scoped
- scope = relation.merge(scope)
- scope.default_scoped = default_scoped
- end
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
else
scope = body
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4fa7cf8a7d..36133bab4c 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -15,9 +15,13 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
- register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
- register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
+ register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
+ register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+
+ register_task(/firebird/, ActiveRecord::Tasks::FirebirdDatabaseTasks)
+ register_task(/sqlserver/, ActiveRecord::Tasks::SqlserverDatabaseTasks)
+ register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks)
def current_config(options = {})
options.reverse_merge! :env => Rails.env
diff --git a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
new file mode 100644
index 0000000000..98014a38ea
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class FirebirdDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.recreate_database!
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -a #{db_string} > #{filename}"
+ end
+
+ def structure_load(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -i #{filename} #{db_string}"
+ end
+
+ private
+
+ def set_firebird_env(config)
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
+ end
+
+ def firebird_db_string(config)
+ FireRuby::Database.db_string_for(config.symbolize_keys)
+ end
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
new file mode 100644
index 0000000000..de3aa50e5e
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class OracleDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.structure_drop.split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ establish_connection(configuration)
+ File.open(filename, "w:utf-8") { |f| f << connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ IO.read(filename).split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
new file mode 100644
index 0000000000..c718ee03a8
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
@@ -0,0 +1,48 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SqlserverDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ test = configuration.deep_dup
+ test_database = test['database']
+ test['database'] = 'master'
+ establish_connection(test)
+ connection.recreate_database!(test_database)
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ Kernel.system("smoscript -s #{configuration['host']} -d #{configuration['database']} -u #{configuration['username']} -p #{configuration['password']} -f #{filename} -A -U")
+ end
+
+ def structure_load(filename)
+ Kernel.system("sqlcmd -S #{configuration['host']} -d #{configuration['database']} -U #{configuration['username']} -P #{configuration['password']} -i #{filename}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
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/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/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0af7cbf74f..e28bb7b6ca 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -114,7 +114,7 @@ module ActiveRecord
end
end
- # test resetting sequences in odd tables in postgreSQL
+ # test resetting sequences in odd tables in PostgreSQL
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
require 'models/movie'
require 'models/subscriber'
@@ -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 contraints 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 5d71effb1f..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
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/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
index 40af317ad1..f4e7a3ef0a 100644
--- a/activerecord/test/cases/adapters/mysql/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql/enum_test.rb
@@ -5,6 +5,6 @@ class MysqlEnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
end
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 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/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index f3a05e48ad..6dd9a5ec87 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -5,6 +5,6 @@ class Mysql2EnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
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/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
index 53002c5265..c0c0e8898c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -35,6 +35,16 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
@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
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 21fb111c91..e78cb88562 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -54,7 +54,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_id_col_that_is_not_primary_key
- test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options|
+ test_copy_table('goofy_string_id', 'goofy_string_id2') do
original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' }
copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' }
assert_equal original_id.type, copied_id.type
@@ -65,7 +65,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_unconventional_primary_key
- test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ test_copy_table('owners', 'owners_unconventional') do
original_pk = @connection.primary_key('owners')
copied_pk = @connection.primary_key('owners_unconventional')
assert_equal original_pk, copied_pk
@@ -90,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/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 10195e3ae4..5536702f58 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -141,7 +141,6 @@ class AggregationsTest < ActiveRecord::TestCase
end
class OverridingAggregationsTest < ActiveRecord::TestCase
- class Name; end
class DifferentName; end
class Person < ActiveRecord::Base
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 3a5dea6f13..4aa6567d85 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -302,7 +302,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_foreign_keys
pets = Pet.all.merge!(:includes => :owner).to_a
- assert_equal 3, pets.length
+ assert_equal 4, pets.length
end
def test_eager_association_loading_with_belongs_to
@@ -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_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 67d18f313a..70c6b489aa 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -583,7 +583,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
- assert_equal 1, owners(:blackbeard).toys.count
+ assert_equal 2, owners(:blackbeard).toys.count
end
def test_find_on_has_many_association_collection_with_include_and_conditions
@@ -882,6 +882,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [tags(:general)], post.reload.tags
end
+ def test_has_many_through_obeys_order_on_through_association
+ owner = owners(:blackbeard)
+ assert owner.toys.to_sql.include?("pets.name desc")
+ assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
+ end
+
test "has many through associations on new records use null relations" do
person = Person.new
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index c8cad84013..ec128acf28 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -235,6 +235,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
end
+ def test_parent_instance_should_be_shared_within_create_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
+ def test_parent_instance_should_be_shared_within_build_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
def test_parent_instance_should_be_shared_with_poked_in_child
m = men(:gordon)
i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
@@ -303,6 +319,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
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_raise_record_not_found_error_when_invalid_ids_are_passed
+ man = Man.create!
+
+ invalid_id = 2394823094892348920348523452345
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_id) }
+
+ invalid_ids = [8432342, 2390102913, 2453245234523452]
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_ids) }
+ end
+
+ def test_raise_record_not_found_error_when_no_ids_are_passed
+ man = Man.create!
+
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() }
+ 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_test.rb b/activerecord/test/cases/associations_test.rb
index a06bacafca..95c571fd03 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -245,7 +245,6 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
class OverridingAssociationsTest < ActiveRecord::TestCase
- class Person < ActiveRecord::Base; end
class DifferentPerson < ActiveRecord::Base; end
class PeopleList < ActiveRecord::Base
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 648596b828..387c741762 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -69,7 +69,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_boolean_attributes
- assert ! Topic.find(1).approved?
+ assert !Topic.find(1).approved?
assert Topic.find(2).approved?
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 7fb50e9617..e57dbd5a09 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -21,7 +21,6 @@ require 'models/parrot'
require 'models/person'
require 'models/edge'
require 'models/joke'
-require 'models/bulb'
require 'models/bird'
require 'models/car'
require 'models/bulb'
@@ -840,7 +839,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
@@ -869,7 +868,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
@@ -880,6 +879,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
@@ -1024,7 +1046,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
@@ -1355,9 +1377,9 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil Thread.current[:UnloadablePost_current_scope]
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.current.value_for(:current_scope, "UnloadablePost")
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil Thread.current[:UnloadablePost_current_scope]
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.current.value_for(:current_scope, "UnloadablePost")
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
@@ -1555,4 +1577,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 87559925e7..ba6b0b1362 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -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 c645523905..b0b647cbf7 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -96,25 +96,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_order_by_grouped_field
- c = Account.all.merge!(:group => :firm_id, :order => "firm_id").sum(:credit_limit)
+ c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
end
def test_should_order_by_calculation
- c = Account.all.merge!(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit)
+ c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit)
assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
assert_equal [6, 2, 9, 1], c.keys.compact
end
def test_should_limit_calculation
- c = Account.all.merge!(:where => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit)
+ c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").limit(2).sum(:credit_limit)
assert_equal [1, 2], c.keys.compact
end
def test_should_limit_calculation_with_offset
- c = Account.all.merge!(:where => "firm_id IS NOT NULL", :group => :firm_id,
- :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit)
+ c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").
+ limit(2).offset(1).sum(:credit_limit)
assert_equal [2, 6], c.keys.compact
end
@@ -164,8 +163,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition
- c = Account.all.merge!(:group => :firm_id,
- :having => 'sum(credit_limit) > 50').sum(:credit_limit)
+ c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -200,17 +198,15 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_with_conditions
- c = Account.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id).sum(:credit_limit)
+ c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_group_by_summed_field_with_conditions_and_having
- c = Account.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id,
- :having => 'sum(credit_limit) > 60').sum(:credit_limit)
+ c = Account.where('firm_id > 1').group(:firm_id).
+ having('sum(credit_limit) > 60').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_nil c[2]
@@ -322,7 +318,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select
Account.update_all("credit_limit = NULL")
- assert_equal 0, Account.all.merge!(:select => "credit_limit").count
+ assert_equal 0, Account.select("credit_limit").count
end
def test_should_count_scoped_select_with_options
@@ -330,11 +326,11 @@ class CalculationsTest < ActiveRecord::TestCase
Account.last.update_columns('credit_limit' => 49)
Account.first.update_columns('credit_limit' => 51)
- assert_equal 1, Account.all.merge!(:select => "credit_limit").where('credit_limit >= 50').count
+ assert_equal 1, Account.select("credit_limit").where('credit_limit >= 50').count
end
def test_should_count_manual_select_with_include
- assert_equal 6, Account.all.merge!(:select => "DISTINCT accounts.id", :includes => :firm).count
+ assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
end
def test_count_with_column_parameter
@@ -366,7 +362,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_field_in_joined_table_with_group_by
- c = Account.all.merge!(:group => 'accounts.firm_id', :joins => :firm).count('companies.id')
+ c = Account.group('accounts.firm_id').joins(:firm).count('companies.id')
[1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index b874adc081..b72c54f97b 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -43,10 +43,20 @@ module ActiveRecord
assert_equal [], coder.load([])
end
- def test_load_swallows_yaml_exceptions
+ def test_load_doesnt_swallow_yaml_exceptions
coder = YAMLColumn.new
bad_yaml = '--- {'
- assert_equal bad_yaml, coder.load(bad_yaml)
+ assert_raises(Psych::SyntaxError) do
+ coder.load(bad_yaml)
+ end
+ end
+
+ def test_load_doesnt_handle_undefined_class_or_module
+ coder = YAMLColumn.new
+ missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'
+ assert_raises(ArgumentError) do
+ coder.load(missing_class_yaml)
+ end
end
end
end
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/column_test.rb b/activerecord/test/cases/column_test.rb
index adbe51f430..3a4f414ae8 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -6,6 +6,9 @@ module ActiveRecord
class ColumnTest < ActiveRecord::TestCase
def test_type_cast_boolean
column = Column.new("field", nil, "boolean")
+ assert column.type_cast('').nil?
+ assert column.type_cast(nil).nil?
+
assert column.type_cast(true)
assert column.type_cast(1)
assert column.type_cast('1')
@@ -15,15 +18,21 @@ module ActiveRecord
assert column.type_cast('TRUE')
assert column.type_cast('on')
assert column.type_cast('ON')
- assert !column.type_cast(false)
- assert !column.type_cast(0)
- assert !column.type_cast('0')
- assert !column.type_cast('f')
- assert !column.type_cast('F')
- assert !column.type_cast('false')
- assert !column.type_cast('FALSE')
- assert !column.type_cast('off')
- assert !column.type_cast('OFF')
+
+ # explicitly check for false vs nil
+ assert_equal false, column.type_cast(false)
+ assert_equal false, column.type_cast(0)
+ assert_equal false, column.type_cast('0')
+ assert_equal false, column.type_cast('f')
+ assert_equal false, column.type_cast('F')
+ assert_equal false, column.type_cast('false')
+ assert_equal false, column.type_cast('FALSE')
+ assert_equal false, column.type_cast('off')
+ assert_equal false, column.type_cast('OFF')
+ assert_equal false, column.type_cast(' ')
+ assert_equal false, column.type_cast("\u3000\r\n")
+ assert_equal false, column.type_cast("\u0000")
+ assert_equal false, column.type_cast('SOMETHING RANDOM')
end
def test_type_cast_integer
@@ -65,8 +74,9 @@ module ActiveRecord
def test_type_cast_time
column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
time_string = Time.now.utc.strftime("%T")
assert_equal time_string, column.type_cast(time_string).strftime("%T")
@@ -74,8 +84,10 @@ module ActiveRecord
def test_type_cast_datetime_and_timestamp
[Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
datetime_string = Time.now.utc.strftime("%FT%T")
assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
@@ -84,8 +96,10 @@ module ActiveRecord
def test_type_cast_date
column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
date_string = Time.now.utc.strftime("%F")
assert_equal date_string, column.type_cast(date_string).strftime("%F")
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 7d06fb5093..ac093251a5 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -115,6 +115,15 @@ 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
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index dbf6005a67..f73e449610 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -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/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8ad40ec3f4..f6cfee0cb8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -576,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 99d54e7526..a9be132801 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -194,6 +194,10 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
end
+ def test_new_with_complex_inheritance
+ assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
+ end
+
def test_new_with_autoload_paths
path = File.expand_path('../../models/autoloadable', __FILE__)
ActiveSupport::Dependencies.autoload_paths << path
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 0c896beb1d..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
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/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 7042d7f4b6..b6e140b912 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -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..db3bb56f1f 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -12,13 +12,13 @@ require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
+require 'models/owner'
require 'models/person'
require 'models/pet'
require 'models/toy'
require 'rexml/document'
class PersistencesTest < ActiveRecord::TestCase
-
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
@@ -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/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1147b9a09e..a48ae1036f 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) || current_adapter?(:Mysql2Adapter) || 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) || 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
@@ -230,6 +242,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_bigint_default
+ output = standard_dump
+ assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ end
+
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
skip unless connection.supports_extensions?
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 239004a223..5a65ad5dfa 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -1,334 +1,6 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/post'
-require 'models/author'
require 'models/developer'
-require 'models/project'
-require 'models/comment'
-require 'models/category'
-require 'models/person'
-require 'models/reference'
-
-class RelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
-
- def test_reverse_order
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
- end
-
- def test_reverse_order_with_arel_node
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
- end
-
- def test_reverse_order_with_multiple_arel_nodes
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_reverse_order_with_arel_nodes_and_strings
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_double_reverse_order_produces_original_order
- assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
- end
-
- def test_scoped_find
- Developer.where("name = 'David'").scoping do
- assert_nothing_raised { Developer.find(1) }
- end
- end
-
- def test_scoped_find_first
- developer = Developer.find(10)
- Developer.where("salary = 100000").scoping do
- assert_equal developer, Developer.order("name").first
- end
- end
-
- def test_scoped_find_last
- highest_salary = Developer.order("salary DESC").first
-
- Developer.order("salary").scoping do
- assert_equal highest_salary, Developer.last
- end
- end
-
- def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.order("salary ASC").first
- highest_salary = Developer.order("salary DESC").first
-
- Developer.order("salary").scoping do
- assert_equal highest_salary, Developer.last
- assert_equal lowest_salary, Developer.first
- end
- end
-
- def test_scoped_find_combines_and_sanitizes_conditions
- Developer.where("salary = 9000").scoping do
- assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
- end
- end
-
- def test_scoped_find_all
- Developer.where("name = 'David'").scoping do
- assert_equal [developers(:david)], Developer.all
- end
- end
-
- def test_scoped_find_select
- Developer.select("id, name").scoping do
- developer = Developer.where("name = 'David'").first
- assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
- end
- end
-
- def test_scope_select_concatenates
- Developer.select("id, name").scoping do
- developer = Developer.select('salary').where("name = 'David'").first
- assert_equal 80000, developer.salary
- assert developer.has_attribute?(:id)
- assert developer.has_attribute?(:name)
- assert developer.has_attribute?(:salary)
- end
- end
-
- def test_scoped_count
- Developer.where("name = 'David'").scoping do
- assert_equal 1, Developer.count
- end
-
- Developer.where('salary = 100000').scoping do
- assert_equal 8, Developer.count
- assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
- end
- end
-
- def test_scoped_find_include
- # with the include, will retrieve only developers for the given project
- scoped_developers = Developer.includes(:projects).scoping do
- Developer.where('projects.id' => 2).to_a
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- end
-
- def test_scoped_find_joins
- scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
- Developer.where('developers_projects.project_id = 2').to_a
- end
-
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_create_with_where
- new_comment = VerySpecialComment.where(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with
- new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with_has_higher_priority
- new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- begin
- Developer.where("name = 'Jamis'").scoping do
- raise "an exception"
- end
- rescue
- end
-
- assert !Developer.all.where_values.include?("name = 'Jamis'")
- end
-
- def test_default_scope_filters_on_joins
- assert_equal 1, DeveloperFilteredOnJoins.all.count
- assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
- end
-
- def test_update_all_default_scope_filters_on_joins
- DeveloperFilteredOnJoins.update_all(:salary => 65000)
- assert_equal 65000, Developer.find(developers(:david).id).salary
-
- # has not changed jamis
- assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
- end
-
- def test_delete_all_default_scope_filters_on_joins
- assert_not_equal [], DeveloperFilteredOnJoins.all
-
- DeveloperFilteredOnJoins.delete_all()
-
- assert_equal [], DeveloperFilteredOnJoins.all
- assert_not_equal [], Developer.all
- end
-end
-
-class NestedRelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
-
- def test_merge_options
- Developer.where('salary = 80000').scoping do
- Developer.limit(10).scoping do
- devs = Developer.all
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_merge_inner_scope_has_priority
- Developer.limit(5).scoping do
- Developer.limit(10).scoping do
- assert_equal 10, Developer.all.size
- end
- end
- end
-
- def test_replace_options
- Developer.where(:name => 'David').scoping do
- Developer.unscoped do
- assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
- end
-
- assert_equal 'David', Developer.first[:name]
- end
- end
-
- def test_three_level_nested_exclusive_scoped_find
- Developer.where("name = 'Jamis'").scoping do
- assert_equal 'Jamis', Developer.first.name
-
- Developer.unscoped.where("name = 'David'") do
- assert_equal 'David', Developer.first.name
-
- Developer.unscoped.where("name = 'Maiha'") do
- assert_equal nil, Developer.first
- end
-
- # ensure that scoping is restored
- assert_equal 'David', Developer.first.name
- end
-
- # ensure that scoping is restored
- assert_equal 'Jamis', Developer.first.name
- end
- end
-
- def test_nested_scoped_create
- comment = Comment.create_with(:post_id => 1).scoping do
- Comment.create_with(:post_id => 2).scoping do
- Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
- end
- end
-
- assert_equal 2, comment.post_id
- end
-
- def test_nested_exclusive_scope_for_create
- comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
- Comment.unscoped.create_with(:post_id => 1).scoping do
- assert Comment.new.body.blank?
- Comment.create :body => "Hey guys"
- end
- end
-
- assert_equal 1, comment.post_id
- assert_equal 'Hey guys', comment.body
- end
-end
-
-class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts, :people, :references
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- def test_forwarding_to_scoped
- assert_equal 4, Comment.search_by_type('Comment').size
- assert_equal 2, @welcome.comments.search_by_type('Comment').size
- end
-
- def test_nested_scope_finder
- Comment.where('1=0').scoping do
- assert_equal 0, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- Comment.where('1=1').scoping do
- assert_equal 2, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-
- def test_should_maintain_default_scope_on_associations
- magician = BadReference.find(1)
- assert_equal [magician], people(:michael).bad_references
- end
-
- def test_should_default_scope_on_associations_is_overriden_by_association_conditions
- reference = references(:michael_unicyclist).becomes(BadReference)
- assert_equal [reference], people(:michael).fixed_bad_references
- end
-
- def test_should_maintain_default_scope_on_eager_loaded_associations
- michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
- magician = BadReference.find(1)
- assert_equal [magician], michael.bad_references
- end
-end
-
-class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
- fixtures :posts, :categories, :categories_posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a category...', Category.what_are_you
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- def test_nested_scope_finder
- Category.where('1=0').scoping do
- assert_equal 0, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- Category.where('1=1').scoping do
- assert_equal 2, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
- end
-end
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
@@ -634,7 +306,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/named_scope_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index b593270352..afe32af1d1 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -6,7 +6,7 @@ require 'models/reply'
require 'models/author'
require 'models/developer'
-class NamedScopeTest < ActiveRecord::TestCase
+class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
@@ -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
@@ -446,4 +459,9 @@ class NamedScopeTest < ActiveRecord::TestCase
end
assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
+
+ def test_subclass_merges_scopes_properly
+ assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
+ end
+
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
new file mode 100644
index 0000000000..0018fc06f2
--- /dev/null
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -0,0 +1,331 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/category'
+require 'models/person'
+require 'models/reference'
+
+class RelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+
+ def test_reverse_order
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
+ end
+
+ def test_reverse_order_with_arel_node
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
+ end
+
+ def test_reverse_order_with_multiple_arel_nodes
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_reverse_order_with_arel_nodes_and_strings
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_double_reverse_order_produces_original_order
+ assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
+ end
+
+ def test_scoped_find
+ Developer.where("name = 'David'").scoping do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ developer = Developer.find(10)
+ Developer.where("salary = 100000").scoping do
+ assert_equal developer, Developer.order("name").first
+ end
+ end
+
+ def test_scoped_find_last
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ lowest_salary = Developer.order("salary ASC").first
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.where("salary = 9000").scoping do
+ assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.where("name = 'David'").scoping do
+ assert_equal [developers(:david)], Developer.all
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.select("id, name").scoping do
+ developer = Developer.where("name = 'David'").first
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scope_select_concatenates
+ Developer.select("id, name").scoping do
+ developer = Developer.select('salary').where("name = 'David'").first
+ assert_equal 80000, developer.salary
+ assert developer.has_attribute?(:id)
+ assert developer.has_attribute?(:name)
+ assert developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scoped_count
+ Developer.where("name = 'David'").scoping do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.where('salary = 100000').scoping do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id' => 2).to_a
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ end
+
+ def test_scoped_find_joins
+ scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
+ Developer.where('developers_projects.project_id = 2').to_a
+ end
+
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
+ def test_scoped_create_with_where
+ new_comment = VerySpecialComment.where(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with
+ new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with_has_higher_priority
+ new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ begin
+ Developer.where("name = 'Jamis'").scoping do
+ raise "an exception"
+ end
+ rescue
+ end
+
+ assert !Developer.all.where_values.include?("name = 'Jamis'")
+ end
+
+ def test_default_scope_filters_on_joins
+ assert_equal 1, DeveloperFilteredOnJoins.all.count
+ assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
+ end
+
+ def test_update_all_default_scope_filters_on_joins
+ DeveloperFilteredOnJoins.update_all(:salary => 65000)
+ assert_equal 65000, Developer.find(developers(:david).id).salary
+
+ # has not changed jamis
+ assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
+ end
+
+ def test_delete_all_default_scope_filters_on_joins
+ assert_not_equal [], DeveloperFilteredOnJoins.all
+
+ DeveloperFilteredOnJoins.delete_all()
+
+ assert_equal [], DeveloperFilteredOnJoins.all
+ assert_not_equal [], Developer.all
+ end
+end
+
+class NestedRelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ Developer.where('salary = 80000').scoping do
+ Developer.limit(10).scoping do
+ devs = Developer.all
+ assert_match '(salary = 80000)', devs.to_sql
+ assert_equal 10, devs.taken
+ end
+ end
+ end
+
+ def test_merge_inner_scope_has_priority
+ Developer.limit(5).scoping do
+ Developer.limit(10).scoping do
+ assert_equal 10, Developer.all.size
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.where(:name => 'David').scoping do
+ Developer.unscoped do
+ assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
+ end
+
+ assert_equal 'David', Developer.first[:name]
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.where("name = 'Jamis'").scoping do
+ assert_equal 'Jamis', Developer.first.name
+
+ Developer.unscoped.where("name = 'David'") do
+ assert_equal 'David', Developer.first.name
+
+ Developer.unscoped.where("name = 'Maiha'") do
+ assert_equal nil, Developer.first
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'David', Developer.first.name
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'Jamis', Developer.first.name
+ end
+ end
+
+ def test_nested_scoped_create
+ comment = Comment.create_with(:post_id => 1).scoping do
+ Comment.create_with(:post_id => 2).scoping do
+ Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
+ end
+ end
+
+ assert_equal 2, comment.post_id
+ end
+
+ def test_nested_exclusive_scope_for_create
+ comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
+ Comment.unscoped.create_with(:post_id => 1).scoping do
+ assert Comment.new.body.blank?
+ Comment.create :body => "Hey guys"
+ end
+ end
+
+ assert_equal 1, comment.post_id
+ assert_equal 'Hey guys', comment.body
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts, :people, :references
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_nested_scope_finder
+ Comment.where('1=0').scoping do
+ assert_equal 0, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ Comment.where('1=1').scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+
+ def test_should_maintain_default_scope_on_associations
+ magician = BadReference.find(1)
+ assert_equal [magician], people(:michael).bad_references
+ end
+
+ 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
+
+ def test_should_maintain_default_scope_on_eager_loaded_associations
+ michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
+ magician = BadReference.find(1)
+ assert_equal [magician], michael.bad_references
+ end
+end
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_nested_scope_finder
+ Category.where('1=0').scoping do
+ assert_equal 0, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ Category.where('1=1').scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/firebird_rake_test.rb b/activerecord/test/cases/tasks/firebird_rake_test.rb
new file mode 100644
index 0000000000..c54989ae34
--- /dev/null
+++ b/activerecord/test/cases/tasks/firebird_rake_test.rb
@@ -0,0 +1,100 @@
+require 'cases/helper'
+
+unless defined?(FireRuby::Database)
+module FireRuby
+ module Database; end
+end
+end
+
+module ActiveRecord
+ module FirebirdSetupper
+ def setup
+ @database = 'db.firebird'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'firebird',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::FirebirdDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::FirebirdAdapter
+ end
+ end
+
+ class FirebirdDBCreateTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBDropTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdStructureDumpTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_dump
+ filename = "filebird.sql"
+ Kernel.expects(:system).with("isql -a #{@database} > #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class FirebirdStructureLoadTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_load
+ filename = "firebird.sql"
+ Kernel.expects(:system).with("isql -i #{filename} #{@database}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/oracle_rake_test.rb b/activerecord/test/cases/tasks/oracle_rake_test.rb
new file mode 100644
index 0000000000..5f840febbc
--- /dev/null
+++ b/activerecord/test/cases/tasks/oracle_rake_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module OracleSetupper
+ def setup
+ @database = 'db.oracle'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'oracle',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::OracleDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::OracleAdapter
+ end
+ end
+
+ class OracleDBCreateTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBDropTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleStructureDumpTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def setup
+ super
+ @connection.stubs(:structure_dump).returns("select sysdate from dual;")
+ end
+
+ def test_structure_dump
+ filename = "oracle.sql"
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+
+ class OracleStructureLoadTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_structure_load
+ filename = "oracle.sql"
+
+ open(filename, 'w') { |f| f.puts("select sysdate from dual;") }
+ @connection.stubs(:execute).with("select sysdate from dual;\n")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 3006a87589..7e7a469edd 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -225,7 +225,7 @@ module ActiveRecord
Kernel.stubs(:system)
end
- def test_structure_dump
+ def test_structure_load
filename = "awesome-file.sql"
Kernel.expects(:system).with("psql -f #{filename} my-app-db")
diff --git a/activerecord/test/cases/tasks/sqlserver_rake_test.rb b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
new file mode 100644
index 0000000000..0f1264b8ce
--- /dev/null
+++ b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module SqlserverSetupper
+ def setup
+ @database = 'db.sqlserver'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlserver',
+ 'database' => @database,
+ 'host' => 'localhost',
+ 'username' => 'username',
+ 'password' => 'password',
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::SqlserverDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::SQLServerAdapter
+ end
+ end
+
+ class SqlserverDBCreateTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBDropTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverStructureDumpTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_dump
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("smoscript -s localhost -d #{@database} -u username -p password -f #{filename} -A -U")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class SqlserverStructureLoadTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_load
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("sqlcmd -S localhost -d #{@database} -U username -P password -i #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 777a2b70dd..9d84f64c96 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -176,6 +176,52 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, owner.updated_at
end
+ def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy1 = klass.find(1)
+ old_pet = toy1.pet
+
+ toy2 = klass.find(2)
+ new_pet = toy2.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ old_pet.update_columns(updated_at: time)
+ new_pet.update_columns(updated_at: time)
+
+ toy1.pet = new_pet
+ toy1.save!
+
+ old_pet.reload
+ new_pet.reload
+
+ assert_not_equal time, new_pet.updated_at
+ assert_not_equal time, old_pet.updated_at
+ end
+
+ def test_clearing_association_touches_the_old_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy = klass.find(1)
+ pet = toy.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ pet.update_columns(updated_at: time)
+
+ toy.pet = nil
+ toy.save!
+
+ pet.reload
+
+ assert_not_equal time, pet.updated_at
+ end
+
def test_timestamp_attributes_for_create
toy = Toy.first
assert_equal toy.send(:timestamp_attributes_for_create), [:created_at, :created_on]
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 29b45944aa..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
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/pets.yml b/activerecord/test/fixtures/pets.yml
index a1601a53f0..2ec4f53e6d 100644
--- a/activerecord/test/fixtures/pets.yml
+++ b/activerecord/test/fixtures/pets.yml
@@ -12,3 +12,8 @@ mochi:
pet_id: 3
name: mochi
owner_id: 2
+
+bulbul:
+ pet_id: 4
+ name: bulbul
+ owner_id: 1
diff --git a/activerecord/test/fixtures/toys.yml b/activerecord/test/fixtures/toys.yml
index 037e335e0a..ae9044ec62 100644
--- a/activerecord/test/fixtures/toys.yml
+++ b/activerecord/test/fixtures/toys.yml
@@ -2,3 +2,13 @@ bone:
toy_id: 1
name: Bone
pet_id: 1
+
+doll:
+ toy_id: 2
+ name: Doll
+ pet_id: 2
+
+bulbuli:
+ toy_id: 3
+ name: Bulbuli
+ pet_id: 4
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/owner.rb b/activerecord/test/models/owner.rb
index fea55f4535..1c7ed4aa3e 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,5 +1,5 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
- has_many :pets
+ has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
end
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index 3cd5bceed5..f7970d7aab 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -1,5 +1,4 @@
class Pet < ActiveRecord::Base
-
attr_accessor :current_user
self.primary_key = :pet_id
@@ -13,5 +12,4 @@ class Pet < ActiveRecord::Base
after_destroy do |record|
Pet.after_destroy_output = record.current_user
end
-
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index f25f72c481..a9a6514c9d 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
@@ -42,7 +52,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index 5401c12ed5..f2cffca52c 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
@@ -53,7 +63,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index d8271ac8d1..6b7012a172 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -32,6 +32,7 @@ ActiveRecord::Schema.define do
char3 text default 'a text field',
positive_integer integer default 1,
negative_integer integer default -1,
+ bigint_default bigint default 0::bigint,
decimal_number decimal(3,2) default 2.78,
multiline_default text DEFAULT '--- []
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a952738e84..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|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b629688591..a4c6ea4236 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,18 @@
## Rails 4.0.0 (unreleased) ##
+* `Class#class_attribute` accepts an `instance_predicate` option which
+ defaults to `true`. If set to `false` the predicate method will not
+ be defined.
+
+ *Agis Anastasopoulos*
+
+* `fast_xs` support has been removed. Use `String#encode(xml: :attr)`.
+
+* `ActiveSupport::Notifications::Instrumenter#instrument` should yield
+ its payload.
+
+ *stopdropandrew*
+
* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context.
Fixes #9772.
@@ -9,6 +22,7 @@
*Charles Jones*
+
## Rails 4.0.0.beta1 (February 25, 2013) ##
* Improve singularizing a singular for multiple cases.
@@ -79,7 +93,7 @@
* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset*
-* Standardise on `to_time` returning an instance of `Time` in the local system timezone
+* Standardize on `to_time` returning an instance of `Time` in the local system timezone
across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`.
*Andrew White*
@@ -145,7 +159,7 @@
* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode`
The encoding scheme was broken for unicode characters outside the basic multilingual plane;
- since json is assumed to be UTF-8, and we already force the encoding to UTF-8,
+ since JSON is assumed to be UTF-8, and we already force the encoding to UTF-8,
simply pass through the un-encoded characters.
*Brett Carter*
@@ -364,7 +378,7 @@
* An optional block can be passed to `HashWithIndifferentAccess#update` and `#merge`.
The block will be invoked for each duplicated key, and used to resolve the conflict,
- thus replicating the behaviour of the corresponding methods on the `Hash` class.
+ thus replicating the behavior of the corresponding methods on the `Hash` class.
*Leo Cassarani*
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/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index f2d9df6d13..6c0cae71ed 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -315,7 +315,7 @@ module ActiveSupport
@config = {
:terminator => "false",
:scope => [ :kind ]
- }.merge(config)
+ }.merge!(config)
end
def compile
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/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 5dc5710c53..39b8cea807 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,4 +1,5 @@
require 'bigdecimal'
+require 'bigdecimal/util'
require 'yaml'
class BigDecimal
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index e51ab9ddbc..6d49b7b6e1 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -44,7 +44,8 @@ class Class
# Base.setting # => []
# Subclass.setting # => [:foo]
#
- # For convenience, a query method is defined as well:
+ # For convenience, an instance predicate method is defined as well.
+ # To skip it, pass <tt>instance_predicate: false</tt>.
#
# Subclass.setting? # => false
#
@@ -72,10 +73,11 @@ class Class
# double assignment is used to avoid "assigned but unused variable" warning
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)
+ instance_predicate = options.fetch(:instance_predicate, true)
attrs.each do |name|
define_singleton_method(name) { nil }
- define_singleton_method("#{name}?") { !!public_send(name) }
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
ivar = "@#{name}"
@@ -109,7 +111,7 @@ class Class
self.class.public_send name
end
end
- define_method("#{name}?") { !!public_send(name) }
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
end
attr_writer name if instance_writer
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 1f78b9eb5a..5b89ace66b 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -93,7 +93,7 @@ module DateAndTime
# Returns a new date/time at the end of the quarter.
# Example: 31st March, 30th June, 30th September.
- # DateTIme objects will have a time set to 23:59:59.
+ # DateTime objects will have a time set to 23:59:59.
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 5d7cb81e38..c656db2c6c 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -4,7 +4,6 @@ require 'active_support/core_ext/string/multibyte'
require 'active_support/core_ext/string/starts_ends_with'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/string/access'
-require 'active_support/core_ext/string/xchar'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb
index afc3032272..ce3a69cf5f 100644
--- a/activesupport/lib/active_support/core_ext/string/indent.rb
+++ b/activesupport/lib/active_support/core_ext/string/indent.rb
@@ -29,7 +29,7 @@ class String
# "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
# "foo".indent(2, "\t") # => "\t\tfoo"
#
- # While +indent_string+ is tipically one space or tab, it may be any string.
+ # While +indent_string+ is typically one space or tab, it may be any string.
#
# The third argument, +indent_empty_lines+, is a flag that says whether
# empty lines should be indented. Default is false.
diff --git a/activesupport/lib/active_support/core_ext/string/xchar.rb b/activesupport/lib/active_support/core_ext/string/xchar.rb
deleted file mode 100644
index f9a5b4fb64..0000000000
--- a/activesupport/lib/active_support/core_ext/string/xchar.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-begin
- # See http://fast-xs.rubyforge.org/ by Eric Wong.
- # Also included with hpricot.
- require 'fast_xs'
-rescue LoadError
- # fast_xs extension unavailable
-else
- begin
- require 'builder'
- rescue LoadError
- # builder demands the first shot at defining String#to_xs
- end
-
- class String
- alias_method :original_xs, :to_xs if method_defined?(:to_xs)
- alias_method :to_xs, :fast_xs
- end
-end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 306d80b2df..837db05dcc 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -78,7 +78,7 @@ module ActiveSupport
end
def self.[](*args)
- new.merge(Hash[*args])
+ new.merge!(Hash[*args])
end
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 71654dbb87..37124fb7ae 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- class DummyKeyGenerator # :nodoc:
+ class LegacyKeyGenerator # :nodoc:
SECRET_MIN_LENGTH = 30 # Characters
def initialize(secret)
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 21a04a9152..c4b64bd1a6 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -53,10 +53,9 @@ module ActiveSupport
class << self
def logger
- if defined?(Rails) && Rails.respond_to?(:logger)
- @logger ||= Rails.logger
+ @logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
+ Rails.logger
end
- @logger
end
attr_writer :logger
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.rb b/activesupport/lib/active_support/notifications.rb
index 705a4693b7..ab2bb5fafe 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -177,7 +177,33 @@ module ActiveSupport
end
def instrumenter
- Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
+ InstrumentationRegistry.instrumenter_for(notifier)
+ end
+ end
+
+ # This class is a registry which holds all of the +Instrumenter+ objects
+ # in a particular thread local. To access the +Instrumenter+ object for a
+ # particular +notifier+, you can call the following method:
+ #
+ # InstrumentationRegistry.instrumenter_for(notifier)
+ #
+ # The instrumenters for multiple notifiers are held in a single instance of
+ # this class.
+ class InstrumentationRegistry # :nodoc:
+ class << self
+ delegate :instrumenter_for, to: :current
+
+ def current
+ Thread.current[:instrumentation_registry] ||= new
+ end
+ end
+
+ def initialize
+ @registry = {}
+ end
+
+ def instrumenter_for(notifier)
+ @registry[notifier] ||= Instrumenter.new(notifier)
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 1ee7ca06bb..0c9a729ce5 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -2,7 +2,7 @@ require 'securerandom'
module ActiveSupport
module Notifications
- # Instrumentors are stored in a thread local.
+ # Instrumenters are stored in a thread local.
class Instrumenter
attr_reader :id
@@ -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/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 4b880cb5dc..21a0620c22 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -151,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",
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/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index acc018fd2d..70a95299ec 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -32,7 +32,7 @@ module ActiveSupport
end
def on_start_element(name, attrs = {})
- new_hash = { CONTENT_KEY => '' }.merge(attrs)
+ new_hash = { CONTENT_KEY => '' }.merge!(attrs)
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 30b94aac47..be2d6a4cb1 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -38,7 +38,7 @@ module ActiveSupport
end
def start_element(name, attrs = [])
- new_hash = { CONTENT_KEY => '' }.merge(Hash[attrs])
+ new_hash = { CONTENT_KEY => '' }.merge!(Hash[attrs])
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb
index 1c3ba8a7a0..e7a1334db3 100644
--- a/activesupport/test/core_ext/class/attribute_test.rb
+++ b/activesupport/test/core_ext/class/attribute_test.rb
@@ -27,7 +27,7 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_equal 1, Class.new(@sub).setting
end
- test 'query method' do
+ test 'predicate method' do
assert_equal false, @klass.setting?
@klass.setting = 1
assert_equal true, @klass.setting?
@@ -48,7 +48,7 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_equal 1, object.setting
end
- test 'instance query' do
+ test 'instance predicate' do
object = @klass.new
assert_equal false, object.setting?
object.setting = 1
@@ -73,6 +73,11 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { object.setting = 'boom' }
end
+ test 'disabling instance predicate' do
+ object = Class.new { class_attribute :setting, instance_predicate: false }.new
+ assert_raise(NoMethodError) { object.setting? }
+ end
+
test 'works well with singleton classes' do
object = @klass.new
object.singleton_class.setting = 'foo'
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 0a1abac767..6781e3c20e 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -24,10 +24,8 @@ class EnumerableTests < ActiveSupport::TestCase
def test_group_by
names = %w(marcel sam david jeremy)
klass = Struct.new(:name)
- objects = (1..50).inject([]) do |people,|
- p = klass.new
- p.name = names.sort_by { rand }.first
- people << p
+ objects = (1..50).map do
+ klass.new names.sample
end
enum = GenericEnumerable.new(objects)
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
index ab1be296f8..a2ae066a21 100644
--- a/activesupport/test/descendants_tracker_with_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -6,7 +6,7 @@ require 'descendants_tracker_test_cases'
class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
- def test_clear_with_autoloaded_parent_children_and_granchildren
+ def test_clear_with_autoloaded_parent_children_and_grandchildren
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
@@ -15,7 +15,7 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
end
end
- def test_clear_with_autoloaded_children_and_granchildren
+ def test_clear_with_autoloaded_children_and_grandchildren
mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
ActiveSupport::DescendantsTracker.clear
assert_equal_sets [Child2], Parent.descendants
@@ -23,7 +23,7 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
end
end
- def test_clear_with_autoloaded_granchildren
+ def test_clear_with_autoloaded_grandchildren
mark_as_autoloaded Grandchild1, Grandchild2 do
ActiveSupport::DescendantsTracker.clear
assert_equal_sets [Child1, Child2], Parent.descendants
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/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index f60f9a58e3..fdc745b23b 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -7,13 +7,13 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_nil a[:not_set]
- a[:allow_concurreny] = true
+ a[:allow_concurrency] = true
assert_equal 1, a.size
- assert a[:allow_concurreny]
+ assert a[:allow_concurrency]
- a[:allow_concurreny] = false
+ a[:allow_concurrency] = false
assert_equal 1, a.size
- assert !a[:allow_concurreny]
+ assert !a[:allow_concurrency]
a["else_where"] = 56
assert_equal 2, a.size
@@ -23,10 +23,10 @@ class OrderedOptionsTest < ActiveSupport::TestCase
def test_looping
a = ActiveSupport::OrderedOptions.new
- a[:allow_concurreny] = true
+ a[:allow_concurrency] = true
a["else_where"] = 56
- test = [[:allow_concurreny, true], [:else_where, 56]]
+ test = [[:allow_concurrency, true], [:else_where, 56]]
a.each_with_index do |(key, value), index|
assert_equal test[index].first, key
@@ -39,13 +39,13 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_nil a.not_set
- a.allow_concurreny = true
+ a.allow_concurrency = true
assert_equal 1, a.size
- assert a.allow_concurreny
+ assert a.allow_concurrency
- a.allow_concurreny = false
+ a.allow_concurrency = false
assert_equal 1, a.size
- assert !a.allow_concurreny
+ assert !a.allow_concurrency
a.else_where = 56
assert_equal 2, a.size
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/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index 2dfdde5c62..904ef7b208 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -176,10 +176,11 @@ if RUBY_PLATFORM =~ /java/
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
else
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 36ac4161ea..e7cb350663 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -141,7 +141,7 @@ class LibxmlEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -193,10 +193,11 @@ class LibxmlEngineTest < ActiveSupport::TestCase
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index 82337961a1..07485911c9 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -141,7 +141,7 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -184,10 +184,11 @@ class LibXMLSAXEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 71f57e43d2..937517786e 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -155,7 +155,7 @@ class NokogiriEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -206,10 +206,11 @@ class NokogiriEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 884494e95e..d4f63f6bd0 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -156,7 +156,7 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
def test_children_with_simple_cdata
@@ -207,10 +207,11 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
private
- def assert_equal_rexml(xml)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
- assert_equal(hash, XmlMini.parse(xml))
- end
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index c4770405f2..70a3b918fd 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -24,6 +24,13 @@ class REXMLEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- XmlMini.parse(io)
+ assert_equal_rexml(io)
end
+
+ private
+ def assert_equal_rexml(xml)
+ parsed_xml = XmlMini.parse(xml)
+ hash = XmlMini.with_backend('REXML') { parsed_xml }
+ assert_equal(hash, parsed_xml)
+ end
end
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/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
new file mode 100644
index 0000000000..2c63342572
--- /dev/null
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -0,0 +1,37 @@
+# Activate the gem you are reporting the issue against.
+gem 'activerecord', '3.2.11'
+require 'active_record'
+require 'minitest/autorun'
+require 'logger'
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :posts do |t|
+ end
+
+ create_table :comments do |t|
+ t.integer :post_id
+ end
+end
+
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+
+class BugTest < MiniTest::Unit::TestCase
+ def test_association_stuff
+ post = Post.create!
+ post.comments << Comment.create!
+
+ assert_equal 1, post.comments.count
+ assert_equal 1, Comment.count
+ assert_equal post.id, Comment.first.post.id
+ end
+end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
new file mode 100644
index 0000000000..68069cdd8d
--- /dev/null
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -0,0 +1,48 @@
+unless File.exists?('Gemfile')
+ File.write('Gemfile', <<-GEMFILE)
+ source 'https://rubygems.org'
+ gem 'rails', github: 'rails/rails'
+ gem 'sqlite3'
+ GEMFILE
+
+ system 'bundle'
+end
+
+require 'bundler'
+Bundler.setup(:default)
+
+require 'active_record'
+require 'minitest/autorun'
+require 'logger'
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :posts do |t|
+ end
+
+ create_table :comments do |t|
+ t.integer :post_id
+ end
+end
+
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+
+class BugTest < MiniTest::Unit::TestCase
+ def test_association_stuff
+ post = Post.create!
+ post.comments << Comment.create!
+
+ assert_equal 1, post.comments.count
+ assert_equal 1, Comment.count
+ assert_equal post.id, Comment.first.post.id
+ end
+end
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index b355c7d91a..f06fc65de6 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', require: false
+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/app/assets/images/rails.png b/guides/code/getting_started/app/assets/images/rails.png
deleted file mode 100644
index d5edc04e65..0000000000
--- a/guides/code/getting_started/app/assets/images/rails.png
+++ /dev/null
Binary files differ
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/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb
index 2e37d93799..3b2ca93ab9 100644
--- a/guides/code/getting_started/config/initializers/session_store.rb
+++ b/guides/code/getting_started/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Blog::Application.config.session_store :encrypted_cookie_store, key: '_blog_session'
+Blog::Application.config.session_store :cookie_store, key: '_blog_session'
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 e01d0d57ea..5e99063da8 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -39,7 +39,7 @@ class ClientsController < ApplicationController
end
```
-As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and run the `new` method. Note that the empty method from the example above could work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
+As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and run the `new` method. Note that the empty method from the example above would work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
```ruby
def new
@@ -113,21 +113,21 @@ To send a hash you include the key name inside the brackets:
</form>
```
-When this form is submitted, the value of `params[:client]` will be `{"name" => "Acme", "phone" => "12345", "address" => {"postcode" => "12345", "city" => "Carrot City"}}`. Note the nested hash in `params[:client][:address]`.
+When this form is submitted, the value of `params[:client]` will be `{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }`. Note the nested hash in `params[:client][:address]`.
-Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash that lets you use symbols and strings interchangeably as keys.
+Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash but lets you use symbols and strings interchangeably as keys.
### JSON parameters
-If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data.
+If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally.
-So for example, if you are sending this JSON parameter:
+So for example, if you are sending this JSON content:
```json
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
```
-You'll get `params[:company]` as `{ :name => "acme", "address" => "123 Carrot Street" }`.
+You'll get `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`.
Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as:
@@ -138,19 +138,19 @@ Also, if you've turned on `config.wrap_parameters` in your initializer or callin
And assume that you're sending the data to `CompaniesController`, it would then be wrapped in `:company` key like this:
```ruby
-{ :name => "acme", :address => "123 Carrot Street", :company => { :name => "acme", :address => "123 Carrot Street" }}
+{ :name => "acme", :address => "123 Carrot Street", :company => { :name => "acme", :address => "123 Carrot Street" } }
```
You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html)
-NOTE: A support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
+NOTE: Support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
### Routing Parameters
The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
```ruby
-match '/clients/:status' => 'clients#index', foo: "bar"
+match '/clients/:status' => 'clients#index', foo: 'bar'
```
In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string. In the same way `params[:action]` will contain "index".
@@ -173,7 +173,7 @@ If you define `default_url_options` in `ApplicationController`, as in the exampl
### Strong Parameters
-With strong parameters Action Controller parameters are forbidden to
+With strong parameters, Action Controller parameters are forbidden to
be used in Active Model mass assignments until they have been
whitelisted. This means you'll have to make a conscious choice about
which attributes to allow for mass updating and thus prevent
@@ -232,15 +232,15 @@ The permitted scalar types are `String`, `Symbol`, `NilClass`,
`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and
`Rack::Test::UploadedFile`.
-To declare that the value in `params+ must be an array of permitted
+To declare that the value in `params` must be an array of permitted
scalar values map the key to an empty array:
```ruby
params.permit(:id => [])
```
-To whitelist an entire hash of parameters, the `permit!+ method can be
-used
+To whitelist an entire hash of parameters, the `permit!` method can be
+used:
```ruby
params.require(:log_entry).permit!
@@ -273,7 +273,7 @@ to having a `name` (any permitted scalar values allowed, too).
You want to also use the permitted attributes in the `new`
action. This raises the problem that you can't use `require` on the
-root-key because normally it does not exist when calling `new`:
+root key because normally it does not exist when calling `new`:
```ruby
# using `fetch` you can supply a default and use
@@ -281,7 +281,7 @@ root-key because normally it does not exist when calling `new`:
params.fetch(:blog, {}).permit(:title, :author)
```
-`accepts_nested_attributes_for` allows you update and destroy the
+`accepts_nested_attributes_for` allows you to update and destroy
associated records. This is based on the `id` and `_destroy`
parameters:
@@ -291,7 +291,7 @@ params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy]
```
Hashes with integer keys are treated differently and you can declare
-the attributes as if they were direct children. You get this kind of
+the attributes as if they were direct children. You get these kinds of
parameters when you use `accepts_nested_attributes_for` in combination
with a `has_many` association:
@@ -311,7 +311,7 @@ in mind. It is not meant as a silver bullet to handle all your
whitelisting problems. However you can easily mix the API with your
own code to adapt to your situation.
-Imagine a situation where you want to whitelist an attribute
+Imagine a scenario where you want to whitelist an attribute
containing a hash with any keys. Using strong parameters you can't
allow a hash with any keys but you can use a simple assignment to get
the job done:
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/action_view_overview.md b/guides/source/action_view_overview.md
index 4cdac43a7e..3b5963efc2 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -492,7 +492,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
#### image_url
-Computes the url to an image asset in the `app/asset/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
+Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
```ruby
image_url("edit.png") # => http://www.example.com/assets/edit.png
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 7355f6816c..2589accadd 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -506,19 +506,15 @@ This code will generate SQL like this:
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
```
-### NOT, LIKE, and NOT LIKE Conditions
+### NOT Conditions
-`NOT`, `LIKE`, and `NOT LIKE` SQL queries can be built by `where.not`, `where.like`, and `where.not_like` respectively.
+`NOT` SQL queries can be built by `where.not`.
```ruby
Post.where.not(author: author)
-
-Author.where.like(name: 'Nari%')
-
-Developer.where.not_like(name: 'Tenderl%')
```
-In other words, these sort of queries can be generated by calling `where` with no argument, then immediately chain with `not`, `like`, or `not_like` passing `where` conditions.
+In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
Ordering
--------
@@ -971,7 +967,7 @@ SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = categories.id
```
-Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).select("distinct(categories.id)")`.
+Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).uniq`.
#### Joining Multiple Associations
@@ -1298,7 +1294,7 @@ recommended that you use the block form of `unscoped`:
```ruby
Client.unscoped {
- Client.created_before(Time.zome.now)
+ Client.created_before(Time.zone.now)
}
```
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 32641d04c1..df39d3c5dc 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -530,6 +530,47 @@ field you should use `validates :field_name, inclusion: { in: [true, false] }`.
The default error message is _"can't be empty"_.
+### `absence`
+
+This helper validates that the specified attributes are absent. It uses the
+`present?` method to check if the value is not either nil or a blank string, that
+is, a string that is either empty or consists of whitespace.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, :login, :email, absence: true
+end
+```
+
+If you want to be sure that an association is absent, you'll need to test
+whether the associated object itself is absent, and not the foreign key used
+to map the association.
+
+```ruby
+class LineItem < ActiveRecord::Base
+ belongs_to :order
+ validates :order, absence: true
+end
+```
+
+In order to validate associated records whose absence is required, you must
+specify the `:inverse_of` option for the association:
+
+```ruby
+class Order < ActiveRecord::Base
+ has_many :line_items, inverse_of: :order
+end
+```
+
+If you validate the absence of an object associated via a `has_one` or
+`has_many` relationship, it will check that the object is neither `present?` nor
+`marked_for_destruction?`.
+
+Since `false.present?` is false, if you want to validate the absence of a boolean
+field you should use `validates :field_name, exclusion: { in: [true, false] }`.
+
+The default error message is _"must be blank"_.
+
### `uniqueness`
This helper validates that the attribute's value is unique right before the
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 517db0d222..101a4f5b42 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1039,6 +1039,8 @@ For convenience `class_attribute` also defines an instance predicate which is th
When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method.
+If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined.
+
NOTE: Defined in `active_support/core_ext/class/attribute.rb`
#### `cattr_reader`, `cattr_writer`, and `cattr_accessor`
@@ -1344,7 +1346,7 @@ The second argument, `indent_string`, specifies which indent string to use. The
"foo".indent(2, "\t") # => "\t\tfoo"
```
-While `indent_string` is tipically one space or tab, it may be any string.
+While `indent_string` is typically one space or tab, it may be any string.
The third argument, `indent_empty_lines`, is a flag that says whether empty lines should be indented. Default is false.
@@ -2198,7 +2200,7 @@ This method accepts three options:
* `:words_connector`: What is used to join the elements of arrays with 3 or more elements, except for the last two. Default is ", ".
* `:last_word_connector`: What is used to join the last items of an array with 3 or more elements. Default is ", and ".
-The defaults for these options can be localised, their keys are:
+The defaults for these options can be localized, their keys are:
| Option | I18n key |
| ---------------------- | ----------------------------------- |
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index d08000eb69..38dbfd3152 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -273,7 +273,7 @@ Action Mailer
to: ["users@rails.com", "ddh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # ommitted for beverity
+ mail: "..." # omitted for brevity
}
```
@@ -299,7 +299,7 @@ Action Mailer
to: ["users@rails.com", "ddh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
- mail: "..." # ommitted for beverity
+ mail: "..." # omitted for brevity
}
```
@@ -428,7 +428,7 @@ end
```
Defining all those block arguments each time can be tedious. You can easily create an `ActiveSupport::Notifications::Event`
-from block args like this:
+from block arguments like this:
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
@@ -442,7 +442,7 @@ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*a
end
```
-Most times you only care about the data itself. Here is a shortuct to just get the data.
+Most times you only care about the data itself. Here is a shortcut to just get the data.
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
@@ -465,7 +465,7 @@ Creating custom events
Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of
all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block.
The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times
-as well as the unique ID. All data passed into the `insturment` call will make it into the payload.
+as well as the unique ID. All data passed into the `instrument` call will make it into the payload.
Here's an example:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index e939606c88..448f0e1f9a 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -547,7 +547,35 @@ This directive is available if the core module that provides this feature was co
If you're compiling nginx with Phusion Passenger you'll need to pass that option when prompted.
-A robust configuration for Apache is possible but tricky; please Google around. (Or help update this Guide if you have a good example configuration for Apache.)
+Apache is also able to serve the [gzipped](http://en.wikipedia.org/wiki/Gzip) version of your assets; however, it requires a bit more work:
+
+```apache
+<LocationMatch "^/assets/.*$">
+ Header unset ETag
+ FileETag None
+
+ # RFC says only cache for 1 year
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
+
+ RewriteEngine On
+ RewriteCond %{HTTP:Accept-Encoding} gzip
+ RewriteCond %{HTTP_USER_AGENT} !Konqueror
+ RewriteCond %{REQUEST_FILENAME}.gz -f
+ RewriteRule ^(.+).(css|js)$ $1.$2.gz [QSA,L]
+</LocationMatch>
+
+<FilesMatch \.css\.gz>
+ ForceType text/css
+</FilesMatch>
+
+<FilesMatch \.js\.gz>
+ ForceType application/javascript
+</FilesMatch>
+AddEncoding gzip .gz
+```
+
+NOTE: You will need to make sure `mod_headers`, `mod_mime` and `mod_rewrite` are loaded; otherwise, the above configuration will fail.
### Local Precompilation
@@ -740,7 +768,7 @@ end
```
Now that you have a `Template` class, it's time to associate it with an
-extenstion for template files:
+extension for template files:
```ruby
Sprockets.register_engine '.bang', BangBang::Template
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 65c8154064..8d203d265a 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -572,7 +572,7 @@ end
These need to be backed up by a migration to create the `assemblies_parts` table. This table should be created without a primary key:
```ruby
-class CreateAssemblyPartJoinTable < ActiveRecord::Migration
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
@@ -1648,9 +1648,10 @@ The `select` method lets you override the SQL `SELECT` clause that is used to re
WARNING: If you specify your own `select`, be sure to include the primary key and foreign key columns of the associated model. If you do not, Rails will throw an error.
-##### `uniq`
+##### `distinct`
-Use the `uniq` method to keep the collection free of duplicates. This is mostly useful together with the `:through` option.
+Use the `distinct` method to keep the collection free of duplicates. This is
+mostly useful together with the `:through` option.
```ruby
class Person < ActiveRecord::Base
@@ -1666,14 +1667,15 @@ person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
```
-In the above case there are two readings and `person.posts` brings out both of them even though these records are pointing to the same post.
+In the above case there are two readings and `person.posts` brings out both of
+them even though these records are pointing to the same post.
-Now let's set `uniq`:
+Now let's set `distinct`:
```ruby
class Person
has_many :readings
- has_many :posts, -> { uniq }, through: :readings
+ has_many :posts, -> { distinct }, through: :readings
end
person = Person.create(name: 'Honda')
@@ -1684,7 +1686,29 @@ person.posts.inspect # => [#<Post id: 7, name: "a1">]
Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
```
-In the above case there are still two readings. However `person.posts` shows only one post because the collection loads only unique records.
+In the above case there are still two readings. However `person.posts` shows
+only one post because the collection loads only unique records.
+
+If you want to make sure that, upon insertion, all of the records in the
+persisted association are distinct (so that you can be sure that when you
+inspect the association that you will never find duplicate records), you should
+add a unique index on the table itself. For example, if you have a table named
+``person_posts`` and you want to make sure all the posts are unique, you could
+add the following in a migration:
+
+```ruby
+add_index :person_posts, :post, :unique => true
+```
+
+Note that checking for uniqueness using something like ``include?`` is subject
+to race conditions. Do not attempt to use ``include?`` to enforce distinctness
+in an association. For instance, using the post example from above, the
+following code would be racy because multiple users could be attempting this
+at the same time:
+
+```ruby
+person.posts << post unless person.posts.include?(post)
+```
#### When are Objects Saved?
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 9d1fb03fab..4711186522 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -82,7 +82,7 @@ The server can be run on a different port using the `-p` option. The default dev
$ rails server -e production -p 4000
```
-The `-b` option binds Rails to the specified ip, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
+The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
### `rails generate`
@@ -414,7 +414,7 @@ app/controllers/admin/users_controller.rb:
* [ 20] [TODO] any other way to do this?
* [132] [FIXME] high priority for next deploy
-app/model/school.rb:
+app/models/school.rb:
* [ 13] [OPTIMIZE] refactor this code to make it faster
* [ 17] [FIXME]
```
@@ -427,7 +427,7 @@ $ rake notes:fixme
app/controllers/admin/users_controller.rb:
* [132] high priority for next deploy
-app/model/school.rb:
+app/models/school.rb:
* [ 17]
```
@@ -436,7 +436,7 @@ You can also use custom annotations in your code and list them using `rake notes
```bash
$ rake notes:custom ANNOTATION=BUG
(in /home/foobar/commandsapp)
-app/model/post.rb:
+app/models/post.rb:
* [ 23] Have to fix this one before pushing!
```
@@ -448,7 +448,7 @@ By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `tes
$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
$ rake notes
(in /home/foobar/commandsapp)
-app/model/user.rb:
+app/models/user.rb:
* [ 35] [FIXME] User should have a subscription at this point
rspec/model/user_spec.rb:
* [122] [TODO] Verify the user that has a subscription works
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index dbbeec7126..9ea493325d 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -646,7 +646,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC".
-* `active_support.initialize_beginning_of_week` Sets the default beginnig of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
+* `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`.
* `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`.
@@ -698,7 +698,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `engines_blank_point` Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run.
-* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railities and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference.
+* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference.
* `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index cc4e369e7d..0be9bb1ced 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -24,12 +24,20 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues).
+If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself — and others — to replicate the bug and figure out a fix.
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
+### Create a Self-Contained gist for Active Record Issues
+
+If you are filing a bug report for Active Record, please use
+[this template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb)
+if the bug is found in a published gem, and
+[this template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
+if the bug happens in the master branch.
+
### Special Treatment for Security Issues
WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The [Rails security policy page](http://rubyonrails.org/security) details the procedure to follow for security issues.
@@ -53,6 +61,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
---------------------
@@ -190,7 +214,7 @@ $ cd rails
$ git checkout -b my_new_branch
```
-It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails Git repository.
+It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
### Write Your Code
@@ -201,6 +225,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
@@ -225,7 +260,7 @@ The above are guidelines — please use your best judgment in using them.
The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
-You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, commiting a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
+You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
A CHANGELOG entry should summarize what was changed and should end with author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 5531dee343..6699098e51 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -23,7 +23,7 @@ One common task is to inspect the contents of a variable. In Rails, you can do t
### `debug`
-The `debug` helper will return a \<pre>-tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
+The `debug` helper will return a \<pre> tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
```html+erb
<%= debug @post %>
@@ -174,7 +174,7 @@ class PostsController < ApplicationController
end
```
-Here's an example of the log generated by this method:
+Here's an example of the log generated when this controller action is executed:
```
Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
@@ -194,11 +194,11 @@ Redirected to #<Post:0x20af760>
Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
```
-Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia.
+Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia.
### Tagged Logging
-When running multi-user, multi-account applications, it’s often useful to be able to filter the logs using some custom rules. `TaggedLogging` in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
+When running multi-user, multi-account applications, it’s often useful to be able to filter the logs using some custom rules. `TaggedLogging` in ActiveSupport helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
```ruby
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
@@ -216,7 +216,7 @@ The debugger can also help you if you want to learn about the Rails source code
### Setup
-Rails uses the `debugger` gem to set breakpoints and step through live code. To install it, just run:
+You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run:
```bash
$ gem install debugger
@@ -235,7 +235,7 @@ class PeopleController < ApplicationController
end
```
-If you see the message in the console or logs:
+If you see this message in the console or logs:
```
***** Debugger requested, but was not available: Start server with --debugger to enable *****
@@ -246,12 +246,12 @@ Make sure you have started your web server with the option `--debugger`:
```bash
$ rails server --debugger
=> Booting WEBrick
-=> Rails 3.0.0 application starting on http://0.0.0.0:3000
+=> Rails 3.2.13 application starting on http://0.0.0.0:3000
=> Debugger enabled
...
```
-TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, if it was started without `--debugger`.
+TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`.
### The Shell
@@ -266,7 +266,7 @@ For example:
(rdb:7)
```
-Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help... so type: `help` (You didn't see that coming, right?)
+Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help`
```
(rdb:7) help
@@ -281,7 +281,7 @@ condition down finish list ps save thread var
continue edit frame method putl set tmate where
```
-TIP: To view the help menu for any command use `help <command-name>` in active debug mode. For example: _`help var`_
+TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_
The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command.
@@ -289,7 +289,7 @@ This command shows you where you are in the code by printing 10 lines centered a
```
(rdb:7) list
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -325,7 +325,7 @@ On the other hand, to see the previous ten lines you should type `list-` (or `l-
```
(rdb:7) l-
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -343,7 +343,7 @@ Finally, to see where you are in the code again you can type `list=`
```
(rdb:7) list=
-[1, 10] in /PathToProject/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
3 # GET /posts.json
@@ -502,7 +502,7 @@ TIP: You can use the debugger while using `rails console`. Just remember to `req
```
$ rails console
-Loading development environment (Rails 3.1.0)
+Loading development environment (Rails 3.2.13)
>> require "debugger"
=> []
>> author = Author.first
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 b8681d493a..3f16ebcf1d 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -423,7 +423,7 @@ Whenever Rails sees that the internal value of an option being generated matches
TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` — you must pass 2. Be aware of values extracted from the `params` hash as they are all strings.
-WARNING: when `:inlude_blank` or `:prompt:` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true.
+WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true.
You can add arbitrary attributes to the options using hashes:
@@ -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/generators.md b/guides/source/generators.md
index d7c789e2d8..a8a34d0ac4 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -589,11 +589,11 @@ Creates an initializer in the `config/initializers` directory of the application
initializer "begin.rb", "puts 'this is the beginning'"
```
-This method also takes a block:
+This method also takes a block, expected to return a string:
```ruby
initializer "begin.rb" do
- puts "Almost done!"
+ "puts 'this is the beginning'"
end
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index cd23b5ee15..3881bb1195 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1761,7 +1761,7 @@ cannot be automatically detected by Rails and corrected.
Two very common sources of data that are not UTF-8:
-* Your text editor: Most text editors (such as Textmate), default to saving files as
+* Your text editor: Most text editors (such as TextMate), default to saving files as
UTF-8. If your text editor does not, this can result in special characters that you
enter in your templates (such as é) to appear as a diamond with a question mark inside
in the browser. This also applies to your i18n translation files.
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index bfd1a7c61b..a3b3472701 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -363,7 +363,7 @@ You can use a symbol to defer the choice of layout until a request is processed:
```ruby
class ProductsController < ApplicationController
- layout "products_layout"
+ layout :products_layout
def show
@product = Product.find(params[:id])
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index bd63970bea..086cf434d9 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -831,7 +831,7 @@ end
```
```ruby
-# app/model/product.rb
+# app/models/product.rb
class Product < ActiveRecord::Base
validates :flag, presence: true
@@ -856,7 +856,7 @@ end
```
```ruby
-# app/model/product.rb
+# app/models/product.rb
class Product < ActiveRecord::Base
validates :flag, :fuzz, presence: true
diff --git a/guides/source/routing.md b/guides/source/routing.md
index d7a4a237ed..04098f0a5c 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -530,7 +530,7 @@ In particular, simple routing makes it very easy to map legacy URLs to new Rails
### Bound Parameters
-When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: `:controller` maps to the name of a controller in your application, and `:action` maps to the name of an action within that controller. For example, consider one of the default Rails routes:
+When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: `:controller` maps to the name of a controller in your application, and `:action` maps to the name of an action within that controller. For example, consider this route:
```ruby
get ':controller(/:action(/:id))'
@@ -850,7 +850,7 @@ resources :user_permissions, controller: 'admin/user_permissions'
This will route to the `Admin::UserPermissions` controller.
-NOTE: Only the directory notation is supported. specifying the
+NOTE: Only the directory notation is supported. Specifying the
controller with ruby constant notation (eg. `:controller =>
'Admin::UserPermissions'`) can lead to routing problems and results in
a warning.
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index a78711f4b2..136dfb4cae 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -65,7 +65,7 @@ HTML Guides
### Generation
-To generate all the guides, just `cd` into the **`guides`** directory, run `bundle install` and execute:
+To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute:
```
bundle exec rake guides:generate
diff --git a/guides/source/security.md b/guides/source/security.md
index 769bd130be..d56ce47b3c 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -432,7 +432,7 @@ Depending on your web application, there may be more ways to hijack the user's a
INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
-But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](http://ambethia.com/recaptcha/) is also a Rails plug-in with the same name as the API.
+But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that he is human, but reveal that a spam robot is a bot.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 1937cbf17a..d5efadec6c 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -128,12 +128,12 @@ When you use `rails generate scaffold`, for a resource among other things it cre
$ rails generate scaffold post title:string body:text
...
create app/models/post.rb
-create test/models/post_test.rb
+create test/unit/post_test.rb
create test/fixtures/posts.yml
...
```
-The default test stub in `test/models/post_test.rb` looks like this:
+The default test stub in `test/unit/post_test.rb` looks like this:
```ruby
require 'test_helper'
@@ -222,10 +222,10 @@ 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 `rails test` command.
+Running a test is as simple as invoking the file containing the test cases through `rake test` command.
```bash
-$ rails test test/models/post_test.rb
+$ rake test test/models/post_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
@@ -233,10 +233,10 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-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`.
+You can also run a particular test method from the test case by running the test and providing the `test method name`.
```bash
-$ rails test test/models/post_test.rb -n test_the_truth
+$ rake test test/models/post_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
@@ -260,7 +260,7 @@ end
Let us run this newly added test.
```bash
-$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
+$ rake test test/models/post_test.rb test_should_not_save_post_without_title
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
@@ -300,7 +300,7 @@ end
Now the test should pass. Let us verify by running the test again:
```bash
-$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title
+$ rake test test/models/post_test.rb test_should_not_save_post_without_title
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
@@ -325,7 +325,7 @@ end
Now you can see even more output in the console from running the tests:
```bash
-$ rails test test/models/post_test.rb -n test_should_report_error
+$ rake test test/models/post_test.rb test_should_report_error
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
@@ -690,9 +690,9 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
get "/login"
assert_response :success
- post_via_redirect "/login", username: users(:avs).username, password: users(:avs).password
+ post_via_redirect "/login", username: users(:david).username, password: users(:david).password
assert_equal '/welcome', path
- assert_equal 'Welcome avs!', flash[:notice]
+ assert_equal 'Welcome david!', flash[:notice]
https!(false)
get "/posts/all"
@@ -715,12 +715,12 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
test "login and browse site" do
# User avs logs in
- avs = login(:avs)
+ avs = login(:david)
# User guest logs in
guest = login(:guest)
# Both are now available in different sessions
- assert_equal 'Welcome avs!', avs.flash[:notice]
+ assert_equal 'Welcome david!', avs.flash[:notice]
assert_equal 'Welcome guest!', guest.flash[:notice]
# User avs can browse site
@@ -761,14 +761,14 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
| 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`|
+| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` as Rails will run all the tests by default|
+| `rake test:controllers` | Runs all the controller tests from `test/controllers`|
+| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`|
+| `rake test:helpers` | Runs all the helper tests from `test/helpers`|
+| `rake test:integration` | Runs all the integration tests from `test/integration`|
+| `rake test:mailers` | Runs all the mailer tests from `test/mailers`|
+| `rake test:models` | Runs all the model tests from `test/models`|
+| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`|
There're also some test commands which you can initiate by running rake tasks:
@@ -927,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:
@@ -951,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
@@ -984,5 +997,6 @@ The built-in `test/unit` based testing is not the only way to test Rails applica
* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
* [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures.
+* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
* [RSpec](http://relishapp.com/rspec), a behavior-driven development framework
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 0941bc7e58..a8182617f3 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -31,6 +31,10 @@ If your application is currently on any version of Rails older than 3.2.x, you s
The following changes are meant for upgrading your application to Rails 4.0.
+### Gemfile
+
+Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that line from your Gemfile when upgrading.
+
### vendor/plugins
Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
@@ -67,7 +71,7 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
* Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail, the error will be attached to `:#{attribute}_confirmation` instead of `attribute`.
-* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behaviour. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file:
+* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behavior. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file:
```ruby
# Disable root element in JSON by default.
@@ -78,17 +82,32 @@ 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/session_store.rb
- Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: 'existing session key'
+ # 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.
+
+If you are relying on the ability for external applications or Javascript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
+
+* Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty.
+As described above, 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'
```
+The same caveats apply here, too. 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. You should also take care to make sure you are not relying on the ability to decode signed cookies generated by your app in external applications or Javascript before upgrading.
+
+Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for details on the move to encrypted session cookies.
+
* Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature.
* Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use `ActionController::Base.default_static_extension` instead.
@@ -103,32 +122,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 correctly prefers the first named route defined in `config/routes.rb` if a clashing route is found later. Check the output of `rake routes` before upgrading and remove unused named routes to avoid issues.
+* 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
- # config/routes.rb
get 'one' => 'test#example', as: :example
get 'two' => 'test#example', as: :example
-
- # Rails 3
- <%= example_path %> # => '/two'
-
- # Rails 4
- <%= example_path %> # => '/one'
```
```ruby
- # config/routes.rb
resources :examples
get 'clashing/:id' => 'test#example', as: :example
-
- # Rails 3
- <%= example_path(1) %> # => '/clashing/1'
-
- # Rails 4
- <%= example_path(1) %> # => '/examples/1'
```
+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
@@ -310,7 +320,7 @@ config.assets.debug = true
Again, most of the changes below are for the asset pipeline. You can read more about these in the [Asset Pipeline](asset_pipeline.html) guide.
```ruby
-# Compress JavaScripts and CSS
+# Compress JavaScript and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 7c4192ee26..ddefaf6ff8 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -394,3 +394,4 @@ Here are some helpful links to help you learn even more:
* [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles)
* [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/)
* [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript)
+* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) \ No newline at end of file
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 3a81e0861b..e4a08f68c1 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,27 @@
## Rails 4.0.0 (unreleased) ##
+* Move rails.png into a data-uri. One less file to get generated into a new
+ application. This is also consistent with the removal of index.html.
+
+ *Steve Klabnik*
+
+* 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*
+
* Don't generate a scaffold.css when --no-assets is specified
*Kevin Glowacz*
@@ -16,39 +38,27 @@
*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
+* Improved `rake test` command for running tests
To run all tests:
- $ rails test
+ $ rake test
To run a test suite
- $ rails test [models,helpers,units,controllers,mailers,...]
+ $ rake 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 ...]
+ $ rake 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.
+ $ rake test test/unit/foo_test.rb TESTOPTS='-n test_the_truth'
- *Prem Sichanugrist and Chris Toomey*
+* Improve service pages with new layout (404, etc).
-* Improve service pages with new layout (404, etc). *Stanislav Sobolev*
+ *Stanislav Sobolev*
## Rails 4.0.0.beta1 (February 25, 2013) ##
@@ -265,17 +275,25 @@
*Derek Prior & Francesco Rodriguez*
-* Fixed support for DATABASE_URL environment variable for rake db tasks. *Grace Liu*
+* Fixed support for `DATABASE_URL` environment variable for rake db tasks.
-* rails dbconsole now can use SSL for MySQL. The database.yml options sslca, sslcert, sslcapath, sslcipher,
- and sslkey now affect rails dbconsole. *Jim Kingdon and Lars Petrus*
+ *Grace Liu*
+
+* `rails dbconsole` now can use SSL for MySQL. The `database.yml` options sslca, sslcert, sslcapath, sslcipher
+ and sslkey now affect `rails dbconsole`.
+
+ *Jim Kingdon and Lars Petrus*
* Correctly handle SCRIPT_NAME when generating routes to engine in application
that's mounted at a sub-uri. With this behavior, you *should not* use
- default_url_options[:script_name] to set proper application's mount point by
- yourself. *Piotr Sarnacki*
+ `default_url_options[:script_name]` to set proper application's mount point by
+ yourself.
+
+ *Piotr Sarnacki*
+
+* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded .
-* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded *José Valim*
+ *José Valim*
* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
@@ -298,21 +316,35 @@
*Aleksey Magusev*
-* Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman*
+* Set `config.active_record.migration_error` to `:page_load` for development.
+
+ *Richard Schneeman*
+
+* Add runner to `Rails::Railtie` as a hook called just after runner starts.
+
+ *José Valim & kennyj*
+
+* Add `/rails/info/routes` path, displays same information as `rake routes` .
-* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
+ *Richard Schneeman & Andrew White*
-* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White*
+* Improved `rake routes` output for redirects.
-* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
+ *Łukasz Strzałkowski & Andrew White*
-* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
+* Load all environments available in `config.paths["config/environments"]`.
-* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
+ *Piotr Sarnacki*
-* Remove Active Resource from Rails framework. *Prem Sichangrist*
+* Remove `Rack::SSL` in favour of `ActionDispatch::SSL`.
-* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki*
+ *Rafael Mendonça França*
+
+* Remove Active Resource from Rails framework.
+
+ *Prem Sichangrist*
+
+* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block.
Example:
@@ -324,12 +356,20 @@
config.console = Pry
end
+ *Piotr Sarnacki*
+
* Add convenience `hide!` method to Rails generators to hide current generator
- namespace from showing when running `rails generate`. *Carlos Antonio da Silva*
+ namespace from showing when running `rails generate`.
-* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
+ *Carlos Antonio da Silva*
+
+* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies.
+
+ *Santiago Pastorino*
* Set config.action_mailer.async = true to turn on asynchronous
- message delivery *Brian Cardarella*
+ message delivery.
+
+ *Brian Cardarella*
Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/railties/CHANGELOG.md) for previous changes.
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/Rakefile b/railties/Rakefile
index eb068fc526..4789f41ad4 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -12,8 +12,9 @@ task :test => 'test:isolated'
namespace :test do
task :isolated do
- dir = ENV["TEST_DIR"] || "**"
- Dir["test/#{dir}/*_test.rb"].each do |file|
+ dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",")
+ test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" }
+ Dir[*test_files].each do |file|
next true if file.include?("fixtures")
dash_i = [
'test',
@@ -22,7 +23,7 @@ namespace :test do
"#{File.dirname(__FILE__)}/../actionpack/lib",
"#{File.dirname(__FILE__)}/../activemodel/lib"
]
- ruby "-I#{dash_i.join ':'}", file
+ ruby "-w", "-I#{dash_i.join ':'}", file
end
end
end
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 6c9c53fc69..1493815c30 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(/^(default$|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..c829873da4
--- /dev/null
+++ b/railties/lib/rails/api/task.rb
@@ -0,0 +1,158 @@
+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
+ ),
+ :exclude => 'lib/rails/generators/rails/**/templates/**/*.rb'
+ }
+ }
+
+ def initialize(name)
+ super
+
+ # Every time rake runs this task is instantiated as all the rest.
+ # Be lazy computing stuff to have as light impact as possible to
+ # the rest of tasks.
+ before_running_rdoc do
+ load_and_configure_sdoc
+ configure_rdoc_files
+ setup_horo_variables
+ end
+ end
+
+ # Hack, ignore the desc calls performed by the original initializer.
+ def desc(description)
+ # no-op
+ end
+
+ def load_and_configure_sdoc
+ require 'sdoc'
+
+ 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'
+ rescue LoadError
+ $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.)
+ exit 1
+ 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 load_and_configure_sdoc
+ 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 0de44984d7..2d5aa9d952 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,5 +1,5 @@
require 'fileutils'
-# FIXME remove DummyKeyGenerator and this require in 4.1
+require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
require 'rails/engine'
@@ -111,7 +111,7 @@ module Rails
key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
- ActiveSupport::DummyKeyGenerator.new(config.secret_token)
+ ActiveSupport::LegacyKeyGenerator.new(config.secret_token)
end
end
end
@@ -122,7 +122,8 @@ 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_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
@@ -135,13 +136,12 @@ module Rails
#
def env_config
@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. " +
- "To convert safely to the encrypted store (without losing existing cookies and sessions), see http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#action-pack"
+ if config.secret_key_base.blank?
+ ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
+ "Read the upgrade documentation to learn more about this new config option."
if config.secret_token.blank?
- raise "You must set config.secret_key_base in your app's config"
+ raise "You must set config.secret_key_base in your app's config."
end
end
@@ -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 41d3722c18..0d1286031c 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -80,15 +80,6 @@ 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/test_runner.rb b/railties/lib/rails/commands/test_runner.rb
deleted file mode 100644
index d8857bd183..0000000000
--- a/railties/lib/rails/commands/test_runner.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-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/engine.rb b/railties/lib/rails/engine.rb
index 579af8c6a5..86f62dfb40 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -107,7 +107,7 @@ module Rails
#
# The <tt>Application</tt> class adds a couple more paths to this set. And as in your
# <tt>Application</tt>, all folders under +app+ are automatically added to the load path.
- # If you have an <tt>app/services/tt> folder for example, it will be added by default.
+ # If you have an <tt>app/services</tt> folder for example, it will be added by default.
#
# == Endpoint
#
@@ -553,7 +553,7 @@ module Rails
#
# This needs to be an initializer, since it needs to run once
# per engine and get the engine as a block parameter
- initializer :set_autoload_paths, before: :bootstrap_hook do |app|
+ initializer :set_autoload_paths, before: :bootstrap_hook do
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 4e703151ba..b53142d20c 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -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'
+ GEMFILE
+ else
+ <<-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'
+ 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
- # 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 CoffeeScript for .js.coffee assets and views
+ gem 'coffee-rails', '~> 4.0.0.beta1'
GEMFILE
end
@@ -211,7 +226,10 @@ module Rails
def javascript_gemfile_entry
unless options[:skip_javascript]
- <<-GEMFILE.strip_heredoc
+ <<-GEMFILE.gsub(/^ {12}/, '').strip_heredoc
+ #{coffee_gemfile_entry}
+ #{javascript_runtime_gemfile_entry}
+
gem '#{options[:javascript]}-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
@@ -221,11 +239,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/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 8b4f52bb3b..d891ba1215 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -40,7 +40,7 @@ module Rails
def indent(content, multiplier = 2)
spaces = " " * multiplier
- content = content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
+ content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
end
def wrap_with_namespace(content)
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index f6bd107eba..07cf31dd83 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', require: false
+end
+
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.0.1'
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep b/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
deleted file mode 100644
index d5edc04e65..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png
+++ /dev/null
Binary files differ
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/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index df07de9922..4a099a4ce2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %>
+<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>
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 ca40914d3b..4fd060341e 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
@@ -6,12 +6,11 @@ class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
ActiveRecord::Migration.check_pending!
- # Uncomment the `fixtures :all` line below to setup all fixtures in test/fixtures/*.yml
- # for all tests in alphabetical order.
+ # 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/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 6574200fbf..1998a392aa 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -52,20 +52,26 @@ Available field types:
`rails generate model product supplier:references{polymorphic}`
- You can also specify some options just after the field type. You can use the
- following options:
+ For integer, string, text and binary fields an integer in curly braces will
+ be set as the limit:
- limit Set the maximum size of the field giving a number between curly braces
- default Set a default value for the field
- precision Defines the precision for the decimal fields
- scale Defines the scale for the decimal fields
- uniq Defines the field values as unique
- index Will add an index on the field
+ `rails generate model user pseudo:string{30}`
- Examples:
+ For decimal two integers separated by a comma in curly braces will be used
+ for precision and scale:
+
+ `rails generate model product price:decimal{10,2}`
+
+ You can add a `:uniq` or `:index` suffix for unique or standard indexes
+ respectively:
- `rails generate model user pseudo:string{30}`
`rails generate model user pseudo:string:uniq`
+ `rails generate model user pseudo:string:index`
+
+ You can combine any single curly brace option with the index options:
+
+ `rails generate model user username:string{30}:uniq`
+ `rails generate model product supplier:references{polymorphic}:index`
Examples:
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 5fe01d0961..850c9d5c0d 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -106,8 +106,6 @@ task default: :test
remove_file "doc"
remove_file "Gemfile"
remove_file "lib/tasks"
- remove_file "app/assets/images/rails.png"
- remove_file "public/index.html"
remove_file "public/robots.txt"
remove_file "README"
remove_file "test"
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 85a8914ccc..58592b4f8e 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -1,8 +1,7 @@
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/core_ext/kernel/reporting'
require 'rails/generators'
+require 'rails/generators/testing/behaviour'
+require 'rails/generators/testing/setup_and_teardown'
+require 'rails/generators/testing/assertions'
require 'fileutils'
module Rails
@@ -27,215 +26,11 @@ module Rails
# setup :prepare_destination
# end
class TestCase < ActiveSupport::TestCase
+ include Rails::Generators::Testing::Behaviour
+ include Rails::Generators::Testing::SetupAndTeardown
+ include Rails::Generators::Testing::Assertions
include FileUtils
- class_attribute :destination_root, :current_path, :generator_class, :default_arguments
-
- # Generators frequently change the current path using +FileUtils.cd+.
- # So we need to store the path at file load and revert back to it after each test.
- self.current_path = File.expand_path(Dir.pwd)
- self.default_arguments = []
-
- def setup # :nodoc:
- destination_root_is_set?
- ensure_current_path
- super
- end
-
- def teardown # :nodoc:
- ensure_current_path
- super
- end
-
- # Sets which generator should be tested:
- #
- # tests AppGenerator
- def self.tests(klass)
- self.generator_class = klass
- end
-
- # Sets default arguments on generator invocation. This can be overwritten when
- # invoking it.
- #
- # arguments %w(app_name --skip-active-record)
- def self.arguments(array)
- self.default_arguments = array
- end
-
- # Sets the destination of generator files:
- #
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
- def self.destination(path)
- self.destination_root = path
- end
-
- # Asserts a given file exists. You need to supply an absolute path or a path relative
- # to the configured destination:
- #
- # assert_file "config/environment.rb"
- #
- # You can also give extra arguments. If the argument is a regexp, it will check if the
- # regular expression matches the given file content. If it's a string, it compares the
- # file with the given string:
- #
- # assert_file "config/environment.rb", /initialize/
- #
- # Finally, when a block is given, it yields the file content:
- #
- # assert_file "app/controllers/products_controller.rb" do |controller|
- # assert_instance_method :index, controller do |index|
- # assert_match(/Product\.all/, index)
- # end
- # end
- def assert_file(relative, *contents)
- absolute = File.expand_path(relative, destination_root)
- assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
-
- read = File.read(absolute) if block_given? || !contents.empty?
- yield read if block_given?
-
- contents.each do |content|
- case content
- when String
- assert_equal content, read
- when Regexp
- assert_match content, read
- end
- end
- end
- alias :assert_directory :assert_file
-
- # Asserts a given file does not exist. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_no_file "config/random.rb"
- def assert_no_file(relative)
- absolute = File.expand_path(relative, destination_root)
- assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
- end
- alias :assert_no_directory :assert_no_file
-
- # Asserts a given migration exists. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_migration "db/migrate/create_products.rb"
- #
- # This method manipulates the given path and tries to find any migration which
- # matches the migration name. For example, the call above is converted to:
- #
- # assert_file "db/migrate/003_create_products.rb"
- #
- # Consequently, assert_migration accepts the same arguments has assert_file.
- def assert_migration(relative, *contents, &block)
- file_name = migration_file_name(relative)
- assert file_name, "Expected migration #{relative} to exist, but was not found"
- assert_file file_name, *contents, &block
- end
-
- # Asserts a given migration does not exist. You need to supply an absolute path or a
- # path relative to the configured destination:
- #
- # assert_no_migration "db/migrate/create_products.rb"
- def assert_no_migration(relative)
- file_name = migration_file_name(relative)
- assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
- end
-
- # Asserts the given class method exists in the given content. This method does not detect
- # class methods inside (class << self), only class methods which starts with "self.".
- # When a block is given, it yields the content of the method.
- #
- # assert_migration "db/migrate/create_products.rb" do |migration|
- # assert_class_method :up, migration do |up|
- # assert_match(/create_table/, up)
- # end
- # end
- def assert_class_method(method, content, &block)
- assert_instance_method "self.#{method}", content, &block
- end
-
- # Asserts the given method exists in the given content. When a block is given,
- # it yields the content of the method.
- #
- # assert_file "app/controllers/products_controller.rb" do |controller|
- # assert_instance_method :index, controller do |index|
- # assert_match(/Product\.all/, index)
- # end
- # end
- def assert_instance_method(method, content)
- assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
- yield $3.strip if block_given?
- end
- alias :assert_method :assert_instance_method
-
- # Asserts the given attribute type gets translated to a field type
- # properly:
- #
- # assert_field_type :date, :date_select
- def assert_field_type(attribute_type, field_type)
- assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
- end
-
- # Asserts the given attribute type gets a proper default value:
- #
- # assert_field_default_value :string, "MyString"
- def assert_field_default_value(attribute_type, value)
- assert_equal(value, create_generated_attribute(attribute_type).default)
- end
-
- # Runs the generator configured for this class. The first argument is an array like
- # command line arguments:
- #
- # class AppGeneratorTest < Rails::Generators::TestCase
- # tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
- # teardown :cleanup_destination_root
- #
- # test "database.yml is not created when skipping Active Record" do
- # run_generator %w(myapp --skip-active-record)
- # assert_no_file "config/database.yml"
- # end
- # end
- #
- # You can provide a configuration hash as second argument. This method returns the output
- # printed by the generator.
- def run_generator(args=self.default_arguments, config={})
- capture(:stdout) { self.generator_class.start(args, config.reverse_merge(destination_root: destination_root)) }
- end
-
- # Instantiate the generator.
- def generator(args=self.default_arguments, options={}, config={})
- @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root))
- end
-
- # Create a Rails::Generators::GeneratedAttribute by supplying the
- # attribute type and, optionally, the attribute name:
- #
- # create_generated_attribute(:string, 'name')
- def create_generated_attribute(attribute_type, name = 'test', index = nil)
- Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
- end
-
- protected
-
- def destination_root_is_set? # :nodoc:
- raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
- end
-
- def ensure_current_path # :nodoc:
- cd current_path
- end
-
- def prepare_destination # :nodoc:
- rm_rf(destination_root)
- mkdir_p(destination_root)
- end
-
- def migration_file_name(relative) # :nodoc:
- absolute = File.expand_path(relative, destination_root)
- dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
- Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
- end
end
end
end
diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb
new file mode 100644
index 0000000000..6267b2f2ee
--- /dev/null
+++ b/railties/lib/rails/generators/testing/assertions.rb
@@ -0,0 +1,121 @@
+module Rails
+ module Generators
+ module Testing
+ module Assertions
+ # Asserts a given file exists. You need to supply an absolute path or a path relative
+ # to the configured destination:
+ #
+ # assert_file "config/environment.rb"
+ #
+ # You can also give extra arguments. If the argument is a regexp, it will check if the
+ # regular expression matches the given file content. If it's a string, it compares the
+ # file with the given string:
+ #
+ # assert_file "config/environment.rb", /initialize/
+ #
+ # Finally, when a block is given, it yields the file content:
+ #
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
+ # assert_match(/Product\.all/, index)
+ # end
+ # end
+ def assert_file(relative, *contents)
+ absolute = File.expand_path(relative, destination_root)
+ assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
+
+ read = File.read(absolute) if block_given? || !contents.empty?
+ yield read if block_given?
+
+ contents.each do |content|
+ case content
+ when String
+ assert_equal content, read
+ when Regexp
+ assert_match content, read
+ end
+ end
+ end
+ alias :assert_directory :assert_file
+
+ # Asserts a given file does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_file "config/random.rb"
+ def assert_no_file(relative)
+ absolute = File.expand_path(relative, destination_root)
+ assert !File.exists?(absolute), "Expected file #{relative.inspect} to not exist, but does"
+ end
+ alias :assert_no_directory :assert_no_file
+
+ # Asserts a given migration exists. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_migration "db/migrate/create_products.rb"
+ #
+ # This method manipulates the given path and tries to find any migration which
+ # matches the migration name. For example, the call above is converted to:
+ #
+ # assert_file "db/migrate/003_create_products.rb"
+ #
+ # Consequently, assert_migration accepts the same arguments has assert_file.
+ def assert_migration(relative, *contents, &block)
+ file_name = migration_file_name(relative)
+ assert file_name, "Expected migration #{relative} to exist, but was not found"
+ assert_file file_name, *contents, &block
+ end
+
+ # Asserts a given migration does not exist. You need to supply an absolute path or a
+ # path relative to the configured destination:
+ #
+ # assert_no_migration "db/migrate/create_products.rb"
+ def assert_no_migration(relative)
+ file_name = migration_file_name(relative)
+ assert_nil file_name, "Expected migration #{relative} to not exist, but found #{file_name}"
+ end
+
+ # Asserts the given class method exists in the given content. This method does not detect
+ # class methods inside (class << self), only class methods which starts with "self.".
+ # When a block is given, it yields the content of the method.
+ #
+ # assert_migration "db/migrate/create_products.rb" do |migration|
+ # assert_class_method :up, migration do |up|
+ # assert_match(/create_table/, up)
+ # end
+ # end
+ def assert_class_method(method, content, &block)
+ assert_instance_method "self.#{method}", content, &block
+ end
+
+ # Asserts the given method exists in the given content. When a block is given,
+ # it yields the content of the method.
+ #
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
+ # assert_match(/Product\.all/, index)
+ # end
+ # end
+ def assert_instance_method(method, content)
+ assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
+ yield $3.strip if block_given?
+ end
+ alias :assert_method :assert_instance_method
+
+ # Asserts the given attribute type gets translated to a field type
+ # properly:
+ #
+ # assert_field_type :date, :date_select
+ def assert_field_type(attribute_type, field_type)
+ assert_equal(field_type, create_generated_attribute(attribute_type).field_type)
+ end
+
+ # Asserts the given attribute type gets a proper default value:
+ #
+ # assert_field_default_value :string, "MyString"
+ def assert_field_default_value(attribute_type, value)
+ assert_equal(value, create_generated_attribute(attribute_type).default)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
new file mode 100644
index 0000000000..7576eba6e0
--- /dev/null
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -0,0 +1,106 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/core_ext/kernel/reporting'
+require 'active_support/concern'
+require 'rails/generators'
+
+module Rails
+ module Generators
+ module Testing
+ module Behaviour
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :destination_root, :current_path, :generator_class, :default_arguments
+
+ # Generators frequently change the current path using +FileUtils.cd+.
+ # So we need to store the path at file load and revert back to it after each test.
+ self.current_path = File.expand_path(Dir.pwd)
+ self.default_arguments = []
+ end
+
+ module ClassMethods
+ # Sets which generator should be tested:
+ #
+ # tests AppGenerator
+ def tests(klass)
+ self.generator_class = klass
+ end
+
+ # Sets default arguments on generator invocation. This can be overwritten when
+ # invoking it.
+ #
+ # arguments %w(app_name --skip-active-record)
+ def arguments(array)
+ self.default_arguments = array
+ end
+
+ # Sets the destination of generator files:
+ #
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ def destination(path)
+ self.destination_root = path
+ end
+ end
+
+ # Runs the generator configured for this class. The first argument is an array like
+ # command line arguments:
+ #
+ # class AppGeneratorTest < Rails::Generators::TestCase
+ # tests AppGenerator
+ # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # teardown :cleanup_destination_root
+ #
+ # test "database.yml is not created when skipping Active Record" do
+ # run_generator %w(myapp --skip-active-record)
+ # assert_no_file "config/database.yml"
+ # end
+ # end
+ #
+ # You can provide a configuration hash as second argument. This method returns the output
+ # printed by the generator.
+ def run_generator(args=self.default_arguments, config={})
+ capture(:stdout) do
+ args += ['--skip-bundle'] unless args.include? '--dev'
+ self.generator_class.start(args, config.reverse_merge(destination_root: destination_root))
+ end
+ end
+
+ # Instantiate the generator.
+ def generator(args=self.default_arguments, options={}, config={})
+ @generator ||= self.generator_class.new(args, options, config.reverse_merge(destination_root: destination_root))
+ end
+
+ # Create a Rails::Generators::GeneratedAttribute by supplying the
+ # attribute type and, optionally, the attribute name:
+ #
+ # create_generated_attribute(:string, 'name')
+ def create_generated_attribute(attribute_type, name = 'test', index = nil)
+ Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
+ end
+
+ protected
+
+ def destination_root_is_set? # :nodoc:
+ raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
+ end
+
+ def ensure_current_path # :nodoc:
+ cd current_path
+ end
+
+ def prepare_destination # :nodoc:
+ rm_rf(destination_root)
+ mkdir_p(destination_root)
+ end
+
+ def migration_file_name(relative) # :nodoc:
+ absolute = File.expand_path(relative, destination_root)
+ dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
+ Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/testing/setup_and_teardown.rb b/railties/lib/rails/generators/testing/setup_and_teardown.rb
new file mode 100644
index 0000000000..73102a283f
--- /dev/null
+++ b/railties/lib/rails/generators/testing/setup_and_teardown.rb
@@ -0,0 +1,18 @@
+module Rails
+ module Generators
+ module Testing
+ module SetupAndTeardown
+ def setup # :nodoc:
+ destination_root_is_set?
+ ensure_current_path
+ super
+ end
+
+ def teardown # :nodoc:
+ ensure_current_path
+ super
+ end
+ end
+ end
+ 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/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index e239e1695e..4c4c80ecda 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -59,7 +59,7 @@
#header {
- background-image: url("/assets/rails.png");
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABACAYAAABY1SR7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAGZhJREFUeNqsWwmUXGWV/t5Sr9aurl6qO0l3Z9/DEoJh18gZQGAUxPHIyQHH7eioZ8bjnAFHZ0RndNxxRBhGcUbxoKIHBkTEcUYREIHIGpKQjUDS6U660/tSVV3Lq/fefPf/Xy2dBFGYx3npqvde/e/e/97v3u/e/8e4Lt2L8DCCAFcGwF8ZBjYbgM1rAZoO+WLwZhDMu9y4+YcOozbAqzwXNA3GdzX/5hV+KnKO2+GXFj/AvzmW8e72iG202CYiphbY403f9/k3QHZtJ9oWtyCQe7wGX79TKVb7rP9pXJPDVxf0Rz+oyxm4HNWrahFNixdk3EAJbERMWOm4ulctVODNVeEVK0DeRVDgb1wfJgcqUo6duaKnFOH7bm6JmH+5LOEgZprwRIHAV3JYfLjKM55Noz3bBqdcgt0Wg52Kq/cHHkXns0qIukKBlltk9rU2QaiouiefPQ+RdBuseAJeqYTK1CTH8mE4NsyIpRWu8nssCs+xULWpjGVwTvieKl/sV6mIXzOib/OftzuG8d6l8SiVMODyRb46oazg8YPP2Wnvy9ISNqplzsxYAW6hjGhHEmYiBoPC+hRMfFMrESgrBC5n0KS+lq1nPahZh2OXymg9bSNWX/u3FKyKI//7Exx96B4Y8RiCEseq8t0VznyxjMDidFIJ8QSf3hJEOFbZEAHVhIkFTX54fxtnIW5pJUQIeZ8ooZShkInuDOLpFIX1ldtCBix7KI/k4E7OwbTjcNIdiCQzsONp2LEk7GgUnZsuQN9lW2En45xlukrUghWzeZq8FsXsi8+gND6MSCqD9k3nwulIUShKZxt0LYPWortRSY0NXreC8J6pZNDChEDh53PT1NIPLaEnLbQKNTETEaR7sycA0jD1INXZAnzObjTbiWh7Vr1A3Knn4nciu+lCvstUig09cp96cVCtcELoFpEIFUjjyIM/osWIg+IMXS3DcfNwZ3NQHmmKU9OqroX2jWdgatduuPkpmA4ViZrK9RqKABEBtg9tDeW+oUIyTIYuFaX7eCG4aqbU+hhKocD3UBoZISBLiC9cpAQKyq5SQo6OjVswtec5VHLTiHUuIN4WonXlqUj2riS0DIUXwZlERFHSK+SQGzqI3MHdmNm7CzMvvowF527B8qvejZ3/+iXk9vVTao5tiTKN0OUHISZEGS/8W6UbRdoTSHe3E1f+CRaR3xhBLVJSIQ7qleZQGBigZYoYdR+ElUjBaW3H6JMPIrV0Hdo2bEayZ7my0KsdLctPBS64EuWZMYw/9wTGnvod0mtzWH71Vuz66o10bVpK8FIx6orUMejpCKYBTvfM9HXBJtA8z3/1BKDivaksVJmaYsgsYPDnd6LzzAuw8I1XUIGleC1HtDWLnguv5BiX4+jDD2D4sQeV1bQvNXBi6vAb1MGtrEEHjRPgqfZ0qMRJElYYSudfq12nmzAvtJ2yib69iRadRGnySD0Uv5bDtCPou/gqnPY3N6DnLRczgtHxCf4aVnUeUdgw6i6FqM1w292Ujo/TJdB5wHcJ2iDCaBTRmVfw4rkw4yksuvQyJJf0YvrgNiayvBLESS9AYuFqJLLLCPb4SQWulosojhxmeCeoDeaQSoVuy8lPtSKxYKnC2Bmf+DwtvBgv3/qfTI6uEtGuJV7PCBTIq5zNtt5uxBgyvap30pf55TISfX1Y/PatGPrVvcgPvEyAJ1GenaPZLSy//G2IL+qki43CNCMwk620iovy9FGUJgYwm8gwpK/guRJOS5dyD688h+n9z2L28F4Ujx2ia04jEl8Ad3oGVTePaGcnQ3sKLb1rkD3nIqx594dRIh733n6PmmrrvGj671sjVlxczRWAkxZ0r+rTrhfMJ0uEM8xKUYXONR+5nr57BdpP24TCsX6M/f5F5AYLWPauK9F11htUwjOIL8GNZH1qpKwiyVGELk0OoDj2EtziFOaODSN3aC/v24xmZzAU51TgcJKd/DktHo9jyRXvg0Or7PvejTj22KPKiyafew6zg8MYypVLNsLkJ2bxaZXM4i5EmCBPsEaoWJUUpfeSK7DgvEtQmh4ihTDQdf5FOHDHr7HqPVeh99KL4OVzpE50N18CtqnCdBCY6rsEcTsqIGUGD6rY9e3bMPzIHmTWLsbqa7ai84wL6YrTqEyOqEmwonEExSoO//R7dLcJWiWCueF+7P7mjZAUY8YdJZqySMo24j5zQSybQdeyhdrX5imho4NhEEnkRbkDQyjSRVJLeziCgef/6avIrFuOtR95P2lJNSSshg4l6rdm+Ht9inWsqIOX7voN+u/eRoEM5PvHMbbjGcwcfg7jO3YxbCcRiaaYQOXnpEaFGeahGQaMCidJRidt8RghS6Q344XQIowmFq2QXdLNdwsx8zUFqCOQNIECVqdp8pESB53Fvhdux9T2FxBb1AWX4XbjDX/HFzjEmgedB4XYKT5D4T0VTLRCtIiTwOBvfovpvS8T+Bm4MyW6jw13tIIDt/9G/TTWk8HKvzgbmd4+YldYQIdixgHJYkC82Ul6UDnQSbEGdsFGZlEWyUyLyiEyYwajRVAoAXNlEjR+pjUCUmiDQcKOORwwgpFfP4cg5mPzTZ9FoqePdGVWuZRPYQNPcgrd0/dCpqpdy3DIsQ4fxtiTu7Hxkx8iRXkcB+94iM86/K0Jx4opi5aOzGJs14toWeLAdYXWxFQCtJlkA+LUq+bI7QR3mj3YoqVNgGcXd5NWUOiZAk9GH86S4jK25jWBLVREl1uK5Voywz6WXf1WLHjTm0lPigSyxoUpnEqU8c26Wyk/Y24RMjhw/yMoj+cQbWvH0isuwuijL6BwaJwcyq7XUTaBP7N3HOU3ke7HSONJb8RTBGoGKZPFyTE8saTZyCPtrC2coxOoTuY5+x4UTzHNsNjR6d6Qa8JJ5BIV8ksVtKzpwcr3v5dyOrzHKMWXizsZAnK6k1ImPDmAqjOmdr9AwXcodzr4kwfQfuY6VKbzyhpGU96S75WxIqb2DaPnvNWKklQD4WSuzB+sVILjOYjm/VARSWKTBQQzlZCFmErYeubzVJJR14SlQtVQMjO0xrXvoulXkq3OKnxAXqSsoSmNUbOM/BV35RjDDz9JrBXpnnEM3vsYjj38LLyZihI8QNAgQhITOCmTO46i+6w1MPm86RVIiC09/RJUGcECCe2UU0G6QIyUjEC5hGaCNd4RqHKU6VuDylQlI2N8hfXDWibEdyhCKXREuZUVUX8lyhh2+Jl5Q/6akSgT4izGn3wBFu+JwYOKj8qwtsbJaYmJuYEZ5AYmFOWXPCN1jTodzeuqM0WtSI1rzXrV0LSNKRFuZLYQ2EYVPjEQVuQUMsCya65GvL1HWUwJS+FNUcBsUiZUQv7aLGlndr+I8ug4XUMVAJw4U7FmI8SFETTmUaGK2gas1SeeP8znoizIEso9DaUIy2FWkNU5V0VYs/azWXKncuCHqgQq1CHiY831H8TGr34erRvXKdD6LD3b+HnRn12qGgdqlmxHZe2aRcy6NbQScl8y8dSOfWQE1yK9YYmqXYww3xhNObemUI2IWraF2d1HMTeeh83MbkUiylKiiMdy2wjzXBjxWYdRiSkhfDVVKGSstxM9l16JxZe/E2+848c49bPXK9D2vPUyEsBOVZMINmpCW6HgEOuIQjXF6FYuAV2aHsWyrVfj9C9er5SR5Kms0PTf8QoZtIo7WSJW+mmRJLGSpDK2ipzV2bK6X6fxtWOCicYVqyhGXkXn+WeTcfape5ZDsPGM91C5iy8LI0s445bd9FkrAFHICt1N8DE+gdyeQczs34+uzeei68LNLGfdea50st6VbiyYmHq+nxTFRSSRVsD3ii7xyeQbdt/M5h/MERMT4i6GjlAWeUxh6HCN8+LIz+5H5zlUbtHSOnVp4MCa51JaIQ16i0kwP9CP0uExPP+JL2DggfuYN8jTJClYxnH4aNimdpp0r7nDkyx9h5gE0+RqSVTyZXXTsMz5FaJyMJrrGLNopyWUIImj//1LjPzuUZLCC5gzVqMwPIglV7/rxCaihFaCPCDOxDUl1EoylFP4mUlFCgPDStLKWB47PnUjrSSsNqrJsa/zR02ZwGjYRoVkEZh0ZHzbfmTPXE85SWrnKip6GeFE2I1iKVBCzNK9pmiVhS1x+Axx7myRJesvgHvvR3rNKmQ3n/OKPVGND1MVXTqHiFK6qVFiwlXgTVDhkq+ChhnyJCW9GeaoIGQOdV0M9YhYZWbvUXrIJJ+rKL6lJ9CYj5Fai0iKqyPkx0HcUsJYrBbtREIJ2H72GxTI/2CL1zAbLkZ8WIxYgUvsKebq6Zl3rEZvymx6echo1N+au9XcS3oHsxWMPrGTFH+CLhsmbhMNRWrNB4SZVSwyJ5WDFRb3DAAmaXf2rPP+6BpbkmStkBLAWwkHmdNWKfYqFaZRp2GGdo+mhpv6bBkNhepRzERpdASeW1aKSZ5RidpoUsRAvQ+NJCnJHHl+bcZ80vjkij661vo/rWMQSitWskgnNv7LP+MNN38NadYuCPtYCItIFTjMRgfeqClkhkFZ+FXCQmpFuyKXii7xNI93LT9szdrUMsNZnJkuwZX6zlKdaqRXrESiq/e19kBC3NisLt+Gc/7jW0gtZ51Bl1MCmUaoM//aRv0aapnF0l362KIUnI6EyuhCUOuWrIVfAZcRAj5NJWJ0C5epP19y1awJLWhdt/a1t3KcGF8Yxb5bbsLItoeYmxZRkRWq46IrR9StX/tcw4oKsYH+nlrZpmbcZQ7R1tDPBvMbdIwofLpVKIfcJy5nCa5WRhnDFkVOx+s5kr29GPzpfUxsuxg0zlQUxSZudG/CqNOSIJxYCclGCA7fDRDpiCK6gIVfidVmWXrHRh0fmBd+eSYIIEcWdRhdJJsWp+aQT1vI9nYjnl3wuhSJLuhAJJ1WQWDisadUELCi0bD1WlscMpq6lrV1Ft0riC9tVcFD8odfDVS9bod5pNGgC3+XFnxsXA2rsw25/gHMTcwiRxdbvLgPsY7s61IktWSZinw6l8SbupNGvUlphB1yZY3aIhfZtRmz4XS3oMoA5JP6BywdvBIr24ytMdzsWjHaMcnI0nXRG5FkdCrnS6gy6QzccxeMZDsJW+r1KbJ4pbKAVy6huXoyauVUaAUjRK5WjN9cH05PCiZl84VfsXaSVTKf191C6F61qCXjtjAORtvTSPb0sgYoEi/UmEmnMj6JkpXA6z2cTAbxxV26GdEEZB12DVVV63BrIuwYaWpCGZyuJBWSFSxPLTB5PH1+rhDDKlQbuvajNUzE+UVyRTTdQt+zWIrGWIJOozo8hjmashq8PkXsZAoty1Yqi/gVnq6ru+p1pUKFTM3dENJzu421TiqKKq3hhUp45apSyM1VGMH0xOi+liz0yOxUyijs2w2DlRjI+8tHB3XUIP+fGBxA9+LFr1kRgwV769p1fPkEQ+9KRq+dKE9MsGKc1BmxltEC7W6CEdW0aUtocIvw0tcSt5JGu3R4OA+zIxW1uKoUOUZzFxmxRp/ai+iz+xi9CK5EVJGdqBNBlG4xdvBlRq9eTQteawhm0MgPLsSGj92gVqjKk8ew/TOfxPjjz8BKxhvLFGHjWUBuJh0Cu6pqD7WCTGz4BDqKpE30rIlj05rw6sKFxuCXPP9O8MEjxQqOTuQwNjJLa1mItaRRGB3GLHnO6znaNmxC/nA/cocPKNoS61iEZVdfEy5LBHVKUieCLY5eeKIiXp6RapJuNVJFMCamYGnOUFyslBo0Xronai0dIfXmnZIqtKhgNIaj/F3ULSLx4j60dnXXy8s/OZe0dyGW7cLOL34arevXI9rayWgYhZPtoJtNqsTbyPKUgwzamyCw867MtG5NBUF9bSBXLCkeKOzDroUutaZODax52yUk5sfgsyrL897+PXtQHTmK7vWnomPpCkSTf3pI7j7/Qmz/5HWY3r5LNziYeC3WPlYsovOJJ7VKVbuPENcgXEyvuV3IbKXpPlcqqh0acqGe2S1oq1jzqmZ+b0mGDJNaM2bnjrHuPnYUifZOtDMKda9ah1RnZ30F99WO9jM2MzouZw0vLdJIuCsiUInOz0vbiVNa9DSBtITyWo3VAV/XG/KmPEuBKrmard7rNxKiyCoN7EBnpXlLCiYTmfibuEHSSSkLV4uzGNr5NEYP7EZb31J0rd6AzMIevtf+g4oIg+7e8iYM3H03J5muw9n3ZquqfwU3aGDdMBqdztr+lXBbhyg+R2xYTb5jN7YG6SKnyh870r8Ki6Py0CiO3fcTNWaCBU3E8FVDr7ZPRjbcDLHO30N/TmazdLk+JFMxVoZh6errUrcmnDQp5o4MocrI4o3N6dmXhp1hoHkOFV2R5CXtVwm3Qc0aBip8Z6lY0HtRpJ8GYz5pVFgxgkaHiaCuDE1gfOAhFdNbJIKxplCKNJqqyoqi0CT9tp9/IyyPE2SryYyDKD9LVKxKUqXbuFOM+yVDN/Rq+0ia1mLmtYNqK8rhTiSpLLNbLkDLuZvQ0X8QBoG+//5fIMjP4AQ/kJkuM+vW+sS1wkgiVSTi0Fq2XqoLFfFYMMkyHSFL2mOpHQmy+aU4xXHoLk6rrIkYiE1JNpZOJjO1ivduOLSkZeuk6/YBwR54jaVv6chXpmZQmJnEssveQjwVcPCXv1IWt4//sUVB7K4WpGTREqhvJCrO5MhtGLMTKWU5pUSpDKs1glhbB4W3VCSpTM6gOl2GQzxJt+RQUMFcOoENrXG0FEhESSvMmIVIZ6uaHL9QZn6Y067VNJueV4bdmYDdktJ7pAJNKKfG+pG/cz8GH/gfGLIARF4o9fs8RWSrUmZxN7Z+9za0sooTPiRuI22bsUMHsevWW1B+iFnYdOgqFWTPPxWnXPdxtK5eV8fB9IH92Pn1m1hz7MQh00Xm/C34+K23MiOXsPvLX8bgbXej5bz1OPs7tzIhduHgnXfghX/8OplEsr6U4ZtV9G69HMvf8wEkKUfgaUeWbs4zX/8Sxm/+AbzxCRVF1VpFM9hrvS2ZmZbuRUh2LpxPw7t60EWK8vgHPoCZ5w4i1pvBps99Bu2nbJ73XLyzB4kvLcAPt27F2LFR9MTjSKbb1L1h4mIq4iNL14u2ZRFJysazZCNHqA0DZXRcuBGnf+bz6v4JLDqVgk3r247DnMdJDkOzffJtDfoY2b0dg08/gbZlq7BiyyWk+MuQ2bAGU9v2snTtQnxBj3pu9OnfYXr/Hiq1EZ0bz0ZsUS+sFUvgDB+DFfh1v3X9Kg4xknfLRNZ21h2/RYTX29avU0pUSwUcuP07KLw0oBZrA5bGozt2MlA5updgzGuJnYyp6rt7778HP37fX+OJW77ZaKzKoo5eOdfRhMehO3+EbXzu8H/dXW/SOTwj0gZqeoVck+h3xES9LDjpVp3QXeRdqSVLkDllrepy5oeHMPH0brq2qdteRmNJwj7pYKFVlr75YrztKw6ya9aFTzF8Tk+pBZrmXRGRdCsSLMiQbKlfE7PLrjarCcSSA0QZvQQevGKncnrfXpVwZTde3+XvqN9b8d4PYfuNn8O+b9zO56K6oGpOiMYreNfSc7eoE+FO00P334XJx3fQzM685zd8/Hqs/uCHGGEy9QEslaT0Cm9t7rVyYqnGWogEGIl+nqUTmyxwTj62HTs/91ks3XqN2u8VBLKZoVt14pe/42oc/O6dzB2+qnEMNGHEPHHbSfiSqloakGP7D7+Dpz79BfT6cRXu5rHatk51Nh9aEaOJu2mOZIf36uDu6EDi3PVoIQGV5efiwSG1Rjny8COY3P4sI1WM2HKx4bpPYdEFlzA9RMOlhCAsLJssYqGxRbcZI8//9MfIrliDvjPOwqqL/xwD996P6rY9zGHWPNMNPf8UJl9+Cdm169G9YWOdapjB8auShsJMc85YdekVWL7lQgroKHd68qMfRcAEu+lrX1GdSdmBKjQn0aOrU9lso5bK53uSLiyscNu10tAy66FganAQD9zwD6jM5ZBe2IeLbvoGWs5YofZQyfKXxbpejl133omfXfth7P/Fz8NRLbXgb0nGNe26GhGST5MzFmEYll2oCl+sd2IZCcWtTKxd6rokwdYVpyK9fB1z1KnIrD0NDt1WiNGB738X3kxJVapiWVmR5pCurc2iSaIkmNJ0Hr+9+WYkMu0YfHI7Dv9+J+766Eew8vSNiFP4WGsGBanhh6bw1K3fRjSdwfSel5FikTT67At4+t9vgVssojA0Rp6VwOyhfjx9262qABrfw1KaJW15YprXvsVcEG1sT5eCji40fXSURVyAvTd9TSmv6nTVifQx/uwzmHiU7kb3Clu+GC27MsY247p07+SihN0m/Kgc6EXRIjmMgDvCF9mcsXJxDgniZSnN3xFLIcc6Yormd1mhCX2QpWc7SteolNUpNUQkIUvJpDkUrsrfqy1L8ZjaFSTrJKLsCbvz6BqxaBwdBReWbJmF3kTa2NYRVYFGHEYKqqFKFXtzMg6uUhaJyzZyQ/d/FdUm8LwmAuYwO/vhQBU+m+ddmy+NpBKNWpIzF7EdRSxrOygMMl6LruUw2tQXOTy1akNFk/XtU/V70H3g6YyNNk5GtOIp/DYvlKp9LoJLWuIl2fADfJ/X71PQ8Jo2Vzbv620OAFI9jtIqCQ7tnfC/JxhNT4dShds4UKvB66s1ftPnRqOh/l13hDDqWGhxqUgTsIV1Fzg5Y7TEpKsK+B/w+sdqUWuqv1CxUN8K/MqHLMnhj/g/J/4/juDky9VSg0kh/zQj322897Pao/8nwAC+AZicLeuzngAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
background-position: top left;
height: 64px;
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
index 87b6f9b5a4..a463380e2d 100644
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ b/railties/lib/rails/test_unit/sub_test_task.rb
@@ -1,6 +1,83 @@
+require 'rake/testtask'
+
module Rails
+ class TestTask < Rake::TestTask # :nodoc: all
+ class TestInfo
+ def initialize(tasks)
+ @tasks = tasks
+ end
+
+ def files
+ @tasks.map { |task|
+ [task, translate(task)].find { |file| test_file?(file) }
+ }.compact
+ end
+
+ def translate(file)
+ if file =~ /^app\/(.*)$/
+ "test/#{$1.sub(/\.rb$/, '')}_test.rb"
+ else
+ "test/#{file}_test.rb"
+ end
+ end
+
+ def tasks
+ @tasks - test_file_tasks - opt_names
+ end
+
+ def opts
+ opts = opt_names
+ if opts.any?
+ "-n #{opts.join ' '}"
+ end
+ end
+
+ private
+
+ def test_file_tasks
+ @tasks.find_all { |task|
+ [task, translate(task)].any? { |file| test_file?(file) }
+ }
+ end
+
+ def test_file?(file)
+ file =~ /^test/ && File.file?(file) && !File.directory?(file)
+ end
+
+ def opt_names
+ (@tasks - test_file_tasks).reject { |t| task_defined? t }
+ end
+
+ def task_defined?(task)
+ Rake::Task.task_defined? task
+ end
+ end
+
+ def self.test_info(tasks)
+ TestInfo.new tasks
+ end
+
+ def initialize(name = :test)
+ super
+ @libs << "test" # lib *and* test seem like a better default
+ end
+
+ def define
+ task @name do
+ if ENV['TESTOPTS']
+ ARGV.replace Shellwords.split ENV['TESTOPTS']
+ end
+ libs = @libs - $LOAD_PATH
+ $LOAD_PATH.unshift(*libs)
+ file_list.each { |fl|
+ FileList[fl].to_a.each { |f| require File.expand_path f }
+ }
+ end
+ end
+ end
+
# Silence the default description to cut down on `rake -T` noise.
- class SubTestTask < Rake::TestTask
+ class SubTestTask < Rake::TestTask # :nodoc:
def desc(string)
# Ignore the description.
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 3c247f32c0..877dd6d254 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -48,10 +48,17 @@ task default: :test
desc 'Runs test:units, test:functionals, test:integration together'
task :test do
- if ENV['TEST']
- exec "bundle exec rails test #{ENV['TEST'].inspect}"
+ info = Rails::TestTask.test_info Rake.application.top_level_tasks
+ if info.files.any?
+ Rails::TestTask.new('test:single') { |t|
+ t.test_files = info.files
+ }
+ ENV['TESTOPTS'] ||= info.opts
+ Rake.application.top_level_tasks.replace info.tasks
+
+ Rake::Task['test:single'].invoke
else
- exec 'bundle exec rails test'
+ Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
end
end
@@ -60,15 +67,11 @@ namespace :test do
# Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
end
- task :run do
- ActiveSupport::Deprecation.warn "`rake test:run` is deprecated. Please use `rails test`."
- exec 'bundle exec rails test'
- end
+ task :run => ['test:units', 'test:functionals', 'test:integration']
# Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
desc "Run tests quickly by merging all types and not resetting db"
- Rake::TestTask.new(:all) do |t|
- t.libs << "test"
+ Rails::TestTask.new(:all) do |t|
t.pattern = "test/**/*_test.rb"
end
@@ -90,7 +93,6 @@ namespace :test do
recent_tests('app/controllers/**/*.rb', 'test/controllers', since) +
recent_tests('app/controllers/**/*.rb', 'test/functional', since)
- t.libs << 'test'
t.test_files = touched.uniq
end
Rake::Task['test:recent'].comment = "Deprecated; Test recent changes"
@@ -114,23 +116,36 @@ namespace :test do
controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" }
(unit_tests + functional_tests).uniq.select { |file| File.exist?(file) }
end
-
- t.libs << 'test'
end
Rake::Task['test:uncommitted'].comment = "Deprecated; Test changes since last checkin (only Subversion and Git)"
- 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}"
+ Rails::TestTask.new(single: "test:prepare")
+
+ Rails::TestTask.new(models: "test:prepare") do |t|
+ t.pattern = 'test/models/**/*_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::TestTask.new(helpers: "test:prepare") do |t|
+ t.pattern = 'test/helpers/**/*_test.rb'
+ end
- exec "bundle exec rails test #{test_suit_name}"
- end
+ Rails::TestTask.new(units: "test:prepare") do |t|
+ t.pattern = 'test/{models,helpers,unit}/**/*_test.rb'
+ end
+
+ Rails::TestTask.new(controllers: "test:prepare") do |t|
+ t.pattern = 'test/controllers/**/*_test.rb'
+ end
+
+ Rails::TestTask.new(mailers: "test:prepare") do |t|
+ t.pattern = 'test/mailers/**/*_test.rb'
+ end
+
+ Rails::TestTask.new(functionals: "test:prepare") do |t|
+ t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb'
+ end
+
+ Rails::TestTask.new(integration: "test:prepare") do |t|
+ t.pattern = 'test/integration/**/*_test.rb'
end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 87fc7690ac..fee352db5a 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,10 +1,10 @@
module Rails
- module VERSION #:nodoc:
+ module VERSION
MAJOR = 4
MINOR = 0
TINY = 0
PRE = "beta1"
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index a55bf012da..45968052a8 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'
@@ -27,6 +27,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'rake', '>= 0.8.7'
- s.add_dependency 'thor', '>= 0.17.0', '< 2.0'
- s.add_dependency 'rdoc', '~> 3.4'
+ s.add_dependency 'thor', '>= 0.18.1', '< 2.0'
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..34432eac3a 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -41,6 +41,7 @@ module ApplicationTests
end
test "assets routes have higher priority" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;"
app_file 'config/routes.rb', <<-RUBY
@@ -86,8 +87,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 +222,8 @@ 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/images/rails.png", "notactuallyapng"
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"
@@ -233,6 +235,8 @@ module ApplicationTests
end
test "precompile shouldn't use the digests present in manifest.json" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
+
app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }"
ENV["RAILS_ENV"] = "production"
@@ -251,6 +255,7 @@ module ApplicationTests
end
test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
+ app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
add_to_config "config.assets.compile = true"
add_to_config "config.assets.digest = true"
@@ -272,7 +277,7 @@ module ApplicationTests
manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
- assert asset_path = assets["assets"].find { |(k, _)| k !~ /rails.png/ && k =~ /.png/ }[1]
+ assert asset_path = assets["assets"].find { |(k, _)| k && k =~ /.png/ }[1]
require "#{app_path}/config/environment"
@@ -431,6 +436,7 @@ module ApplicationTests
end
test "asset urls should be protocol-relative if no request is in scope" do
+ app_file "app/assets/images/rails.png", "notreallyapng"
app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";'
add_to_config "config.assets.precompile = %w{image_loader.js}"
add_to_config "config.asset_host = 'example.com'"
@@ -441,6 +447,7 @@ module ApplicationTests
test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do
ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"
+ app_file "app/assets/images/rails.png", "notreallyapng"
app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";'
add_to_config "config.assets.precompile = %w{app.js}"
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 af495bb450..31bc003dcb 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -126,37 +126,28 @@ 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(
+ Process.spawn(
"#{app_path}/bin/rails console --sandbox",
in: @slave, out: @slave, err: @slave
)
assert_output "> ", 30
- pid
end
def test_sandbox
- pid = spawn_console
+ spawn_console
write_prompt "Post.count", "=> 0"
write_prompt "Post.create"
write_prompt "Post.count", "=> 1"
+ @master.puts "quit"
- kill pid
-
- pid = spawn_console
+ 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/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index f0d3438aa4..91c5807379 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -1,5 +1,4 @@
require 'isolation/abstract_unit'
-# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
module ApplicationTests
@@ -10,7 +9,7 @@ module ApplicationTests
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
'action_dispatch.show_exceptions' => false,
- 'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+ 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
)
endpoint = Proc.new do |e|
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index a5fdfbf887..8cb0dfeb63 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -157,10 +157,6 @@ module ApplicationTests
end
RUBY
- add_to_config <<-RUBY
- config.session_store :encrypted_cookie_store, key: '_myapp_session'
- RUBY
-
require "#{app_path}/config/environment"
get '/foo/write_session'
@@ -178,7 +174,7 @@ module ApplicationTests
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
end
- test "session using upgrade signature to encryption cookie store works the same way as encrypted cookie store" do
+ test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
get ':controller(/:action)'
@@ -208,7 +204,6 @@ module ApplicationTests
add_to_config <<-RUBY
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
RUBY
require "#{app_path}/config/environment"
@@ -228,7 +223,7 @@ module ApplicationTests
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo']
end
- test "session using upgrade signature to encryption cookie store upgrades session to encrypted mode" do
+ test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
get ':controller(/:action)'
@@ -264,7 +259,6 @@ module ApplicationTests
add_to_config <<-RUBY
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
RUBY
require "#{app_path}/config/environment"
@@ -287,5 +281,63 @@ module ApplicationTests
get '/foo/read_raw_cookie'
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo']
end
+
+ test "session upgrading legacy signed cookies to new signed cookies" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
+ render nothing: true
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ render nothing: true
+ end
+
+ def read_session
+ render text: session[:foo]
+ end
+
+ def read_signed_cookie
+ render text: cookies.signed[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render text: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.secret_key_base = nil
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get '/foo/write_raw_session'
+ get '/foo/read_session'
+ assert_equal '1', last_response.body
+
+ get '/foo/write_session'
+ get '/foo/read_session'
+ assert_equal '2', last_response.body
+
+ get '/foo/read_signed_cookie'
+ assert_equal '2', last_response.body
+
+ verifier = ActiveSupport::MessageVerifier.new(app.config.secret_token)
+
+ get '/foo/read_raw_cookie'
+ assert_equal 2, verifier.verify(last_response.body)['foo']
+ end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a9e0e1bcb7..eb590da678 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -84,20 +84,8 @@ module ApplicationTests
Dir.chdir(app_path){ `rake stats` }
end
- def test_rake_test_error_output
- Dir.chdir(app_path){ `rake db:migrate` }
-
- app_file "test/models/one_model_test.rb", <<-RUBY
- raise 'models'
- RUBY
-
- silence_stderr do
- output = Dir.chdir(app_path) { `rake test 2>&1` }
- assert_match 'models', output
- end
- end
-
def test_rake_test_uncommitted_always_find_git_in_parent_dir
+ return "FIXME :'("
app_name = File.basename(app_path)
app_dir = File.dirname(app_path)
moved_app_name = app_name + '_moved'
@@ -129,13 +117,10 @@ module ApplicationTests
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|
+ %w(recent uncommitted).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
+ 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
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 56ca3bc1a9..1cf53aa4fb 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -15,14 +15,6 @@ module ApplicationTests
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'
@@ -37,14 +29,9 @@ module ApplicationTests
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'
+ create_test_file :models, 'bar'
assert_match "1 tests, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb")
end
@@ -62,24 +49,14 @@ module ApplicationTests
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
+ assert_match "syntax error", error_stream.read
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|
+ run_test_models_command.tap do |output|
assert_match "FooTest", output
assert_match "BarTest", output
assert_match "2 tests, 2 assertions, 0 failures", output
@@ -90,7 +67,7 @@ module ApplicationTests
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|
+ run_test_helpers_command.tap do |output|
assert_match "FooHelperTest", output
assert_match "BarHelperTest", output
assert_match "2 tests, 2 assertions, 0 failures", output
@@ -102,7 +79,7 @@ module ApplicationTests
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|
+ run_test_units_command.tap do |output|
assert_match "FooTest", output
assert_match "BarHelperTest", output
assert_match "BazUnitTest", output
@@ -114,7 +91,7 @@ module ApplicationTests
create_test_file :controllers, 'foo_controller'
create_test_file :controllers, 'bar_controller'
create_test_file :models, 'foo'
- run_test_command('controllers').tap do |output|
+ run_test_controllers_command.tap do |output|
assert_match "FooControllerTest", output
assert_match "BarControllerTest", output
assert_match "2 tests, 2 assertions, 0 failures", output
@@ -125,7 +102,7 @@ module ApplicationTests
create_test_file :mailers, 'foo_mailer'
create_test_file :mailers, 'bar_mailer'
create_test_file :models, 'foo'
- run_test_command('mailers').tap do |output|
+ run_test_mailers_command.tap do |output|
assert_match "FooMailerTest", output
assert_match "BarMailerTest", output
assert_match "2 tests, 2 assertions, 0 failures", output
@@ -137,7 +114,7 @@ module ApplicationTests
create_test_file :controllers, 'bar_controller'
create_test_file :functional, 'baz_functional'
create_test_file :models, 'foo'
- run_test_command('functionals').tap do |output|
+ run_test_functionals_command.tap do |output|
assert_match "FooMailerTest", output
assert_match "BarControllerTest", output
assert_match "BazFunctionalTest", output
@@ -148,7 +125,7 @@ module ApplicationTests
def test_run_integration
create_test_file :integration, 'foo_integration'
create_test_file :models, 'foo'
- run_test_command('integration').tap do |output|
+ run_test_integration_command.tap do |output|
assert_match "FooIntegration", output
assert_match "1 tests, 1 assertions, 0 failures", output
end
@@ -178,17 +155,31 @@ module ApplicationTests
end
RUBY
- run_test_command('test/unit/chu_2_koi_test.rb -n test_rikka').tap do |output|
+ run_test_command('test/unit/chu_2_koi_test.rb 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')
+ def test_run_matched_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 /rikka/').tap do |output|
+ assert_match "Rikka", output
+ assert_no_match "Sanae", output
+ end
end
def test_load_fixtures_when_running_test_suites
@@ -199,11 +190,18 @@ module ApplicationTests
suites.each do |suite, directory|
directory ||= suite
create_fixture_test directory
- assert_match "3 users", run_test_command(suite)
+ assert_match "3 users", run_task(["test:#{suite}"])
Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" }
end
end
+ def test_run_with_model
+ create_model_with_fixture
+ create_fixture_test 'models', 'user'
+ assert_match "3 users", run_task(["test models/user"])
+ assert_match "3 users", run_task(["test app/models/user.rb"])
+ end
+
def test_run_different_environment_using_env_var
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -220,6 +218,7 @@ module ApplicationTests
end
def test_run_different_environment_using_e_tag
+ env = "development"
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -230,7 +229,7 @@ module ApplicationTests
end
RUBY
- assert_match "development", run_test_command('-e development test/unit/env_test.rb')
+ assert_match env, run_test_command("test/unit/env_test.rb RAILS_ENV=#{env}")
end
def test_generated_scaffold_works_with_rails_test
@@ -239,8 +238,17 @@ module ApplicationTests
end
private
+ def run_task(tasks)
+ Dir.chdir(app_path) { `bundle exec rake #{tasks.join ' '}` }
+ end
+
def run_test_command(arguments = 'test/unit/test_test.rb')
- Dir.chdir(app_path) { `bundle exec rails test #{arguments}` }
+ run_task ['test', arguments]
+ end
+ %w{ mailers models helpers units controllers functionals integration }.each do |type|
+ define_method("run_test_#{type}_command") do
+ run_task ["test:#{type}"]
+ end
end
def create_model_with_fixture
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 3c784b43be..a34beaedb3 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -114,9 +114,10 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match('dev', options[:environment])
end
- private
-
attr_reader :output
+ private :output
+
+ private
def start(argv = [])
rails_console = Rails::Console.new(app, parse_arguments(argv))
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 38fe8ca544..edb92b3aa2 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -172,8 +172,10 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
assert_match(/Usage:.*dbconsole/, stdout)
end
- private
attr_reader :aborted, :output
+ private :aborted, :output
+
+ private
def dbconsole
@dbconsole ||= Rails::DBConsole.new(nil)
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 0697035871..5fdf58c8ee 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
@@ -300,6 +303,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /# gem 'debugger'/
end
+ def test_inclusion_of_lazy_loaded_sdoc
+ run_generator
+ assert_file 'Gemfile', /gem 'sdoc', require: false/
+ end
+
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -338,7 +346,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_new_hash_style
run_generator [destination_root]
assert_file "config/initializers/session_store.rb" do |file|
- assert_match(/config.session_store :encrypted_cookie_store, key: '_.+_session'/, file)
+ assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 34441ef679..ac71fb5884 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -124,7 +124,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip_active_record"]
assert_no_file "test/dummy/config/database.yml"
assert_file "test/test_helper.rb" do |contents|
- assert_no_match /ActiveRecord/, contents
+ assert_no_match(/ActiveRecord/, contents)
end
end
@@ -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_test.rb b/railties/test/generators_test.rb
index 9953aa929b..361784f509 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -164,7 +164,7 @@ class GeneratorsTest < Rails::Generators::TestCase
Rails::Generators.invoke "super_shoulda:model", ["Account"]
end
- def test_developer_options_are_overwriten_by_user_options
+ def test_developer_options_are_overwritten_by_user_options
Rails::Generators.options[:with_options] = { generate: false }
self.class.class_eval(<<-end_eval, __FILE__, __LINE__ + 1)
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 26b388b6f9..01fa2c6864 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -187,7 +187,7 @@ module RailtiesTest
assert_equal "Hello bukkits\n", response[2].body
end
- test "adds its views to view paths with lower proriority than app ones" do
+ test "adds its views to view paths with lower priority than app ones" do
@plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
class BukkitController < ActionController::Base
def index
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/railties/test/test_info_test.rb b/railties/test/test_info_test.rb
new file mode 100644
index 0000000000..d5463c11de
--- /dev/null
+++ b/railties/test/test_info_test.rb
@@ -0,0 +1,59 @@
+require 'abstract_unit'
+require 'rails/test_unit/sub_test_task'
+
+module Rails
+ class TestInfoTest < ActiveSupport::TestCase
+ def test_test_files
+ info = new_test_info ['test']
+ assert_predicate info.files, :empty?
+ assert_nil info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_file
+ info = new_test_info ['test', __FILE__]
+ assert_equal [__FILE__], info.files
+ assert_nil info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_opts
+ info = new_test_info ['test', __FILE__, '/foo/']
+ assert_equal [__FILE__], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_model_shorthand
+ info = new_test_info ['test', 'models/foo', '/foo/']
+
+ def info.test_file?(file)
+ file == "test/models/foo_test.rb" || super
+ end
+
+ assert_equal ['test/models/foo_test.rb'], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def test_with_model_path
+ info = new_test_info ['test', 'app/models/foo.rb', '/foo/']
+
+ def info.test_file?(file)
+ file == "test/models/foo_test.rb" || super
+ end
+
+ assert_equal ['test/models/foo_test.rb'], info.files
+ assert_equal '-n /foo/', info.opts
+ assert_equal ['test'], info.tasks
+ end
+
+ def new_test_info(tasks)
+ Class.new(TestTask::TestInfo) {
+ def task_defined?(task)
+ task == "test"
+ end
+ }.new tasks
+ end
+ end
+end
diff --git a/tasks/release.rb b/tasks/release.rb
index 650b381e0f..0c22f812fc 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -23,20 +23,25 @@ directory "pkg"
file = Dir[glob].first
ruby = File.read(file)
- major, minor, tiny, pre = version.split('.')
- pre = pre ? pre.inspect : "nil"
+ if framework == "rails" || framework == "railties"
+ 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*)MAJOR(\s*)= .*?$/, "\\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*)MINOR(\s*)= .*?$/, "\\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*)TINY(\s*)= .*?$/, "\\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*)PRE(\s*)= .*?$/, "\\1PRE = #{pre}")
+ raise "Could not insert PRE in #{file}" unless $1
+ else
+ ruby.gsub!(/^(\s*)Gem::Version\.new .*?$/, "\\1Gem::Version.new \"#{version}\"")
+ raise "Could not insert Gem::Version in #{file}" unless $1
+ end
File.open(file, 'w') { |f| f.write ruby }
end
diff --git a/version.rb b/version.rb
index 87fc7690ac..fee352db5a 100644
--- a/version.rb
+++ b/version.rb
@@ -1,10 +1,10 @@
module Rails
- module VERSION #:nodoc:
+ module VERSION
MAJOR = 4
MINOR = 0
TINY = 0
PRE = "beta1"
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end