aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikel Lindsaar <raasdnil@gmail.com>2010-01-17 12:46:51 +1100
committerMikel Lindsaar <raasdnil@gmail.com>2010-01-17 12:46:51 +1100
commit6f663addaa7ed40f1133687d7a2be0958bf0c059 (patch)
tree7beb7f092c1979d1fd5ea6e7aaaf4f59c2f4abf4
parent8834b2612b7ddda70ee6a685eb0063d3daa8e63d (diff)
parent3e94032227d450d479f511070c51f37f53d0ecc4 (diff)
downloadrails-6f663addaa7ed40f1133687d7a2be0958bf0c059.tar.gz
rails-6f663addaa7ed40f1133687d7a2be0958bf0c059.tar.bz2
rails-6f663addaa7ed40f1133687d7a2be0958bf0c059.zip
Merge branch 'master' of github.com:lifo/docrails
-rw-r--r--.gitignore5
-rw-r--r--.gitmodules3
-rw-r--r--Gemfile4
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb40
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb3
-rw-r--r--actionmailer/lib/action_mailer/railties/subscriber.rb20
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb3
-rw-r--r--actionmailer/test/abstract_unit.rb1
-rw-r--r--actionmailer/test/mail_service_test.rb41
-rw-r--r--actionmailer/test/subscriber_test.rb53
-rw-r--r--actionpack/CHANGELOG8
-rw-r--r--actionpack/lib/abstract_controller.rb1
-rw-r--r--actionpack/lib/abstract_controller/base.rb5
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb6
-rw-r--r--actionpack/lib/abstract_controller/url_for.rb156
-rw-r--r--actionpack/lib/action_controller.rb13
-rw-r--r--actionpack/lib/action_controller/base.rb9
-rw-r--r--actionpack/lib/action_controller/caching.rb11
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb18
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb8
-rw-r--r--actionpack/lib/action_controller/deprecated/dispatcher.rb31
-rw-r--r--actionpack/lib/action_controller/dispatch/dispatcher.rb52
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb12
-rw-r--r--actionpack/lib/action_controller/metal/filter_parameter_logging.rb22
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb177
-rw-r--r--actionpack/lib/action_controller/metal/head.rb3
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb102
-rw-r--r--actionpack/lib/action_controller/metal/logger.rb89
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb9
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb4
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb4
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb38
-rw-r--r--actionpack/lib/action_controller/railtie.rb67
-rw-r--r--actionpack/lib/action_controller/railties/subscriber.rb60
-rw-r--r--actionpack/lib/action_controller/test_case.rb7
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb226
-rw-r--r--actionpack/lib/action_dispatch.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb123
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb101
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb50
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb391
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb80
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb48
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb129
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb174
-rw-r--r--actionpack/lib/action_dispatch/middleware/head.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/string_coercion.rb29
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb245
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb118
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb1
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb68
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb13
-rw-r--r--actionpack/lib/action_view/locale/en.yml40
-rw-r--r--actionpack/lib/action_view/railtie.rb17
-rw-r--r--actionpack/lib/action_view/railties/subscriber.rb24
-rw-r--r--actionpack/lib/action_view/render/partials.rb17
-rw-r--r--actionpack/lib/action_view/render/rendering.rb24
-rw-r--r--actionpack/lib/action_view/template/text.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb3
-rw-r--r--actionpack/test/abstract/url_for_test.rb272
-rw-r--r--actionpack/test/abstract_unit.rb30
-rw-r--r--actionpack/test/active_record_unit.rb1
-rw-r--r--actionpack/test/activerecord/controller_runtime_test.rb50
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb2
-rw-r--r--actionpack/test/controller/base_test.rb88
-rw-r--r--actionpack/test/controller/caching_test.rb14
-rw-r--r--actionpack/test/controller/dispatcher_test.rb80
-rw-r--r--actionpack/test/controller/filter_params_test.rb12
-rw-r--r--actionpack/test/controller/flash_test.rb42
-rw-r--r--actionpack/test/controller/integration_test.rb13
-rw-r--r--actionpack/test/controller/logging_test.rb80
-rw-r--r--actionpack/test/controller/mime_responds_test.rb130
-rw-r--r--actionpack/test/controller/resources_test.rb52
-rw-r--r--actionpack/test/controller/routing_test.rb4
-rw-r--r--actionpack/test/controller/subscriber_test.rb192
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb283
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb107
-rw-r--r--actionpack/test/dispatch/request_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb263
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb23
-rw-r--r--actionpack/test/dispatch/string_coercion_test.rb40
-rw-r--r--actionpack/test/fixtures/respond_with/using_defaults.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb (renamed from actionpack/test/fixtures/respond_with/using_defaults.html.erb)0
-rw-r--r--actionpack/test/lib/controller/fake_models.rb2
-rw-r--r--actionpack/test/template/active_model_helper_i18n_test.rb12
-rw-r--r--actionpack/test/template/date_helper_test.rb22
-rw-r--r--actionpack/test/template/form_helper_test.rb103
-rw-r--r--actionpack/test/template/form_options_helper_i18n_test.rb6
-rw-r--r--actionpack/test/template/javascript_helper_test.rb5
-rw-r--r--actionpack/test/template/prototype_helper_test.rb5
-rw-r--r--actionpack/test/template/subscriber_test.rb102
-rw-r--r--actionpack/test/template/text_helper_test.rb14
-rw-r--r--activemodel/CHANGELOG2
-rw-r--r--activemodel/lib/active_model.rb7
-rw-r--r--activemodel/lib/active_model/errors.rb5
-rw-r--r--activemodel/lib/active_model/locale/en.yml49
-rw-r--r--activemodel/lib/active_model/serializers/json.rb8
-rw-r--r--activemodel/lib/active_model/test_case.rb2
-rw-r--r--activemodel/lib/active_model/translation.rb5
-rw-r--r--activemodel/lib/active_model/validations.rb65
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb20
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb8
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/format.rb30
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/length.rb69
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb10
-rw-r--r--activemodel/lib/active_model/validations/presence.rb3
-rw-r--r--activemodel/lib/active_model/validations/validates.rb106
-rw-r--r--activemodel/lib/active_model/validations/with.rb47
-rw-r--r--activemodel/lib/active_model/validator.rb67
-rw-r--r--activemodel/test/cases/helper.rb1
-rw-r--r--activemodel/test/cases/tests_database.rb2
-rw-r--r--activemodel/test/cases/translation_test.rb5
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb36
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb90
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb8
-rw-r--r--activemodel/test/cases/validations/validates_test.rb114
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb22
-rw-r--r--activemodel/test/models/custom_reader.rb2
-rw-r--r--activemodel/test/models/person.rb6
-rw-r--r--activemodel/test/models/person_with_validator.rb11
-rw-r--r--activemodel/test/validators/email_validator.rb6
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/association_preload.rb33
-rwxr-xr-xactiverecord/lib/active_record/associations.rb247
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb5
-rw-r--r--activerecord/lib/active_record/autosave_association.rb67
-rwxr-xr-xactiverecord/lib/active_record/base.rb309
-rw-r--r--activerecord/lib/active_record/calculations.rb75
-rw-r--r--activerecord/lib/active_record/callbacks.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb7
-rw-r--r--activerecord/lib/active_record/locale/en.yml44
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb10
-rw-r--r--activerecord/lib/active_record/named_scope.rb8
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb53
-rw-r--r--activerecord/lib/active_record/railtie.rb30
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb15
-rw-r--r--activerecord/lib/active_record/railties/databases.rake65
-rw-r--r--activerecord/lib/active_record/railties/subscriber.rb27
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/relation.rb95
-rw-r--r--activerecord/lib/active_record/relation/calculation_methods.rb19
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb208
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb130
-rw-r--r--activerecord/lib/active_record/schema.rb6
-rw-r--r--activerecord/lib/active_record/test_case.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/associated.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb11
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb28
-rw-r--r--activerecord/test/cases/autosave_association_test.rb83
-rwxr-xr-xactiverecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/callbacks_test.rb45
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb3
-rw-r--r--activerecord/test/cases/method_scoping_test.rb65
-rw-r--r--activerecord/test/cases/modules_test.rb4
-rw-r--r--activerecord/test/cases/multiple_db_test.rb4
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb39
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb15
-rw-r--r--activerecord/test/cases/reflection_test.rb58
-rw-r--r--activerecord/test/cases/relations_test.rb8
-rw-r--r--activerecord/test/cases/subscriber_test.rb51
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb98
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb10
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb99
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb65
-rw-r--r--activerecord/test/models/bird.rb6
-rw-r--r--activerecord/test/models/invoice.rb4
-rw-r--r--activerecord/test/models/line_item.rb3
-rw-r--r--activerecord/test/models/parrot.rb6
-rw-r--r--activerecord/test/models/pirate.rb6
-rw-r--r--activerecord/test/models/ship.rb6
-rw-r--r--activerecord/test/schema/schema.rb10
-rw-r--r--activeresource/lib/active_resource/connection.rb14
-rw-r--r--activeresource/lib/active_resource/railtie.rb11
-rw-r--r--activeresource/lib/active_resource/railties/subscriber.rb15
-rw-r--r--activeresource/test/abstract_unit.rb2
-rw-r--r--activeresource/test/cases/subscriber_test.rb39
-rw-r--r--activesupport/CHANGELOG3
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/float/rounding.rb5
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb3
-rw-r--r--activesupport/lib/active_support/notifications.rb14
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb8
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb7
-rw-r--r--activesupport/test/abstract_unit.rb1
-rw-r--r--activesupport/test/core_ext/duration_test.rb25
-rw-r--r--activesupport/test/notifications_test.rb40
m---------rack0
-rw-r--r--railties/CHANGELOG4
-rw-r--r--railties/builtin/rails_info/rails/info.rb4
-rw-r--r--railties/builtin/rails_info/rails_info_controller.rb2
-rw-r--r--railties/guides/rails_guides/generator.rb2
-rw-r--r--railties/guides/rails_guides/indexer.rb4
-rw-r--r--railties/guides/source/active_support_core_extensions.textile83
-rw-r--r--railties/guides/source/layout.html.erb2
-rw-r--r--railties/lib/rails.rb28
-rw-r--r--railties/lib/rails/application.rb144
-rw-r--r--railties/lib/rails/bootstrap.rb156
-rw-r--r--railties/lib/rails/commands/console.rb11
-rw-r--r--railties/lib/rails/commands/dbconsole.rb10
-rw-r--r--railties/lib/rails/commands/destroy.rb2
-rwxr-xr-xrailties/lib/rails/commands/generate.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/commands/server.rb18
-rw-r--r--railties/lib/rails/configuration.rb53
-rw-r--r--railties/lib/rails/deprecation.rb38
-rw-r--r--railties/lib/rails/dispatcher.rb2
-rw-r--r--railties/lib/rails/generators/base.rb17
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb19
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html1
-rwxr-xr-xrailties/lib/rails/generators/rails/app/templates/script/console.tt4
-rwxr-xr-xrailties/lib/rails/generators/rails/app/templates/script/dbconsole.tt4
-rwxr-xr-xrailties/lib/rails/generators/rails/app/templates/script/runner3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb25
-rw-r--r--railties/lib/rails/generators/test_case.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb4
-rw-r--r--railties/lib/rails/plugin.rb1
-rw-r--r--railties/lib/rails/rack.rb1
-rw-r--r--railties/lib/rails/rack/log_tailer.rb4
-rw-r--r--railties/lib/rails/rack/metal.rb59
-rw-r--r--railties/lib/rails/railtie.rb4
-rw-r--r--railties/lib/rails/subscriber.rb109
-rw-r--r--railties/lib/rails/subscriber/test_helper.rb118
-rw-r--r--railties/lib/rails/tasks/misc.rake1
-rw-r--r--railties/lib/rails/test_help.rb17
-rw-r--r--railties/lib/rails/vendor/bundler/LICENSE20
-rw-r--r--railties/lib/rails/vendor/bundler/README.markdown162
-rw-r--r--railties/lib/rails/vendor/bundler/Rakefile57
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler.rb34
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/cli.rb44
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/commands/bundle_command.rb31
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/commands/exec_command.rb31
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/dependency.rb56
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/dsl.rb109
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/environment.rb111
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/finder.rb51
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/gem_bundle.rb11
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/gem_ext.rb25
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/repository.rb151
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/repository/directory_repository.rb46
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/repository/gem_repository.rb108
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/resolver.rb189
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/runtime.rb2
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/source.rb150
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/templates/app_script.erb3
-rw-r--r--railties/lib/rails/vendor/bundler/lib/bundler/templates/environment.erb127
-rw-r--r--railties/test/abstract_unit.rb1
-rw-r--r--railties/test/application/generators_test.rb2
-rw-r--r--railties/test/application/initializer_test.rb11
-rw-r--r--railties/test/application/metal_test.rb86
-rw-r--r--railties/test/application/middleware_test.rb80
-rw-r--r--railties/test/application/notifications_test.rb46
-rw-r--r--railties/test/application/test_test.rb63
-rw-r--r--railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb5
-rw-r--r--railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb5
-rw-r--r--railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb5
-rw-r--r--railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb5
-rw-r--r--railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb7
-rw-r--r--railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb7
-rw-r--r--railties/test/generators/app_generator_test.rb22
-rw-r--r--railties/test/generators/generators_test_helper.rb6
-rw-r--r--railties/test/initializer/path_test.rb8
-rw-r--r--railties/test/metal_test.rb101
-rw-r--r--railties/test/plugins/configuration_test.rb9
-rw-r--r--railties/test/plugins/vendored_test.rb8
-rw-r--r--railties/test/rails_info_controller_test.rb1
-rw-r--r--railties/test/subscriber_test.rb130
317 files changed, 6122 insertions, 5559 deletions
diff --git a/.gitignore b/.gitignore
index 9a65e4996f..4082dd9a51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-.DS_Store
debug.log
doc/rdoc
activemodel/doc
@@ -17,10 +16,6 @@ railties/doc/guides/html/images
railties/doc/guides/html/stylesheets
benches
railties/guides/output
-*.rbc
-*.swp
-*.swo
-*.tmproj
bin
vendor/gems/
railties/tmp
diff --git a/.gitmodules b/.gitmodules
index d0be7ff194..fd4fd34d3e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
[submodule "arel"]
path = arel
url = git://github.com/rails/arel.git
-[submodule "rack"]
- path = rack
- url = git://github.com/rails/rack.git
diff --git a/Gemfile b/Gemfile
index 2aca5065f5..81b7ce4d49 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,9 +7,6 @@ gem "rails", "3.0.pre", :path => "railties"
gem lib, '3.0.pre', :path => lib
end
-# AS
-gem "i18n", ">= 0.3.0"
-
# AR
gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git"
gem "sqlite3-ruby", ">= 1.2.5"
@@ -20,7 +17,6 @@ only :test do
end
# AP
-gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git"
gem "rack-test", "0.5.3"
gem "RedCloth", ">= 4.2.2"
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 8adea46d35..96549bf29c 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 3.0.pre')
- s.add_dependency('mail', '~> 1.5.0')
+ s.add_dependency('mail', '~> 1.6.0')
s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
s.has_rdoc = true
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 25b8e4874e..5be1beaedb 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -259,10 +259,9 @@ module ActionMailer #:nodoc:
include AbstractController::LocalizedCache
include AbstractController::Layouts
include AbstractController::Helpers
+ include AbstractController::UrlFor
helper ActionMailer::MailHelper
-
- include ActionController::UrlWriter
include ActionMailer::DeprecatedBody
private_class_method :new #:nodoc:
@@ -399,10 +398,12 @@ module ActionMailer #:nodoc:
# ...
# end
# end
- def receive(raw_email)
- logger.info "Received mail:\n #{raw_email}" unless logger.nil?
- mail = Mail.new(raw_email)
- new.receive(mail)
+ def receive(raw_mail)
+ ActiveSupport::Notifications.instrument("action_mailer.receive") do |payload|
+ mail = Mail.new(raw_mail)
+ set_payload_for_mail(payload, mail)
+ new.receive(mail)
+ end
end
# Deliver the given mail object directly. This can be used to deliver
@@ -424,7 +425,19 @@ module ActionMailer #:nodoc:
self.view_paths = ActionView::Base.process_view_paths(root)
end
+ def set_payload_for_mail(payload, mail) #:nodoc:
+ payload[:message_id] = mail.message_id
+ payload[:subject] = mail.subject
+ payload[:to] = mail.to
+ payload[:from] = mail.from
+ payload[:bcc] = mail.bcc if mail.bcc.present?
+ payload[:cc] = mail.cc if mail.cc.present?
+ payload[:date] = mail.date
+ payload[:mail] = mail.encoded
+ end
+
private
+
def matches_dynamic_method?(method_name) #:nodoc:
method_name = method_name.to_s
/^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
@@ -495,17 +508,14 @@ module ActionMailer #:nodoc:
def deliver!(mail = @mail)
raise "no mail object available for delivery!" unless mail
- if logger
- logger.info "Sent mail to #{Array(recipients).join(', ')}"
- logger.debug "\n#{mail.encoded}"
- end
-
- ActiveSupport::Notifications.instrument(:deliver_mail, :mail => mail) do
- begin
+ begin
+ ActiveSupport::Notifications.instrument("action_mailer.deliver",
+ :template => template, :mailer => self.class.name) do |payload|
+ self.class.set_payload_for_mail(payload, mail)
self.delivery_method.perform_delivery(mail) if perform_deliveries
- rescue Exception => e # Net::SMTP errors or sendmail pipe errors
- raise e if raise_delivery_errors
end
+ rescue Exception => e # Net::SMTP errors or sendmail pipe errors
+ raise e if raise_delivery_errors
end
mail
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 5410c7d75f..b05d21ae5d 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -5,6 +5,9 @@ module ActionMailer
class Railtie < Rails::Railtie
plugin_name :action_mailer
+ require "action_mailer/railties/subscriber"
+ subscriber ActionMailer::Railties::Subscriber.new
+
initializer "action_mailer.set_configs" do |app|
app.config.action_mailer.each do |k,v|
ActionMailer::Base.send "#{k}=", v
diff --git a/actionmailer/lib/action_mailer/railties/subscriber.rb b/actionmailer/lib/action_mailer/railties/subscriber.rb
new file mode 100644
index 0000000000..af9c477237
--- /dev/null
+++ b/actionmailer/lib/action_mailer/railties/subscriber.rb
@@ -0,0 +1,20 @@
+module ActionMailer
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def deliver(event)
+ recipients = Array(event.payload[:to]).join(', ')
+ info("Sent mail to #{recipients} (%1.fms)" % event.duration)
+ debug("\n#{event.payload[:mail]}")
+ end
+
+ def receive(event)
+ info("Received mail (%.1fms)" % event.duration)
+ debug("\n#{event.payload[:mail]}")
+ end
+
+ def logger
+ ActionMailer::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index e8632d4559..318a1e46d1 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -1,6 +1,3 @@
-require 'active_support/test_case'
-require 'action_mailer/base'
-
module ActionMailer
class NonInferrableMailerError < ::StandardError
def initialize(name)
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index af6f1bc92e..50b8a53006 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -10,7 +10,6 @@ require 'rubygems'
require 'test/unit'
require 'action_mailer'
-require 'action_mailer/test_case'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index f66b4a174b..cd41739f1a 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -688,40 +688,6 @@ class ActionMailerTest < Test::Unit::TestCase
TestMailer.deliver_signed_up(@recipient)
end
- class FakeLogger
- attr_reader :info_contents, :debug_contents
-
- def initialize
- @info_contents, @debug_contents = "", ""
- end
-
- def info(str = nil, &blk)
- @info_contents << str if str
- @info_contents << blk.call if block_given?
- end
-
- def debug(str = nil, &blk)
- @debug_contents << str if str
- @debug_contents << blk.call if block_given?
- end
- end
-
- def test_delivery_logs_sent_mail
- mail = TestMailer.create_signed_up(@recipient)
- # logger = mock()
- # logger.expects(:info).with("Sent mail to #{@recipient}")
- # logger.expects(:debug).with("\n#{mail.encoded}")
- TestMailer.logger = FakeLogger.new
- TestMailer.deliver_signed_up(@recipient)
- assert(TestMailer.logger.info_contents =~ /Sent mail to #{@recipient}/)
- expected = TestMailer.logger.debug_contents
- actual = "\n#{mail.encoded}"
- expected.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
- actual.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
-
- assert_equal(expected, actual)
- end
-
def test_unquote_quoted_printable_subject
msg = <<EOF
From: me@example.com
@@ -1110,10 +1076,15 @@ EOF
assert_equal "another@somewhere.test", mail['return-path'].to_s
end
+ def test_return_path_with_create
+ mail = TestMailer.create_return_path
+ assert_equal "another@somewhere.test", mail.return_path
+ end
+
def test_return_path_with_deliver
ActionMailer::Base.delivery_method = :smtp
TestMailer.deliver_return_path
- assert_match %r{^Return-Path: another@somewhere.test}, MockSMTP.deliveries[0][0]
+ assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
assert_equal "another@somewhere.test", MockSMTP.deliveries[0][1].to_s
end
diff --git a/actionmailer/test/subscriber_test.rb b/actionmailer/test/subscriber_test.rb
new file mode 100644
index 0000000000..01a71f481d
--- /dev/null
+++ b/actionmailer/test/subscriber_test.rb
@@ -0,0 +1,53 @@
+require "abstract_unit"
+require "rails/subscriber/test_helper"
+require "action_mailer/railties/subscriber"
+
+module SubscriberTest
+ Rails::Subscriber.add(:action_mailer, ActionMailer::Railties::Subscriber.new)
+
+ class TestMailer < ActionMailer::Base
+ def basic
+ recipients "somewhere@example.com"
+ subject "basic"
+ from "basic@example.com"
+ render :text => "Hello world"
+ end
+
+ def receive(mail)
+ # Do nothing
+ end
+ end
+
+ def set_logger(logger)
+ ActionMailer::Base.logger = logger
+ end
+
+ def test_deliver_is_notified
+ TestMailer.deliver_basic
+ wait
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Sent mail to somewhere@example.com/, @logger.logged(:info).first
+ assert_equal 1, @logger.logged(:debug).size
+ assert_match /Hello world/, @logger.logged(:debug).first
+ end
+
+ def test_receive_is_notified
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
+ TestMailer.receive(fixture)
+ wait
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Received mail/, @logger.logged(:info).first
+ assert_equal 1, @logger.logged(:debug).size
+ assert_match /Jamis/, @logger.logged(:debug).first
+ end
+
+ class SyncSubscriberTest < ActionMailer::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include SubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActionMailer::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include SubscriberTest
+ end
+end \ No newline at end of file
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 782b4229fb..014a501080 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,13 @@
*Edge*
+* Fixed that PrototypeHelper#update_page should return html_safe [DHH]
+
+* Fixed that much of DateHelper wouldn't return html_safe? strings [DHH]
+
+* Fixed that fragment caching should return a cache hit as html_safe (or it would all just get escaped) [DHH]
+
+* Added that ActionController::Base now does helper :all instead of relying on the default ApplicationController in Rails to do it [DHH]
+
* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH]
* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples:
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 237ab577ba..efc35a7e56 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -15,4 +15,5 @@ module AbstractController
autoload :LocalizedCache
autoload :Logger
autoload :Rendering
+ autoload :UrlFor
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index a6889d5d01..48725ad82a 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -86,6 +86,11 @@ module AbstractController
abstract!
+ # Initialize controller with nil formats.
+ def initialize #:nodoc:
+ @_formats = nil
+ end
+
# Calls the action going through the entire action dispatch stack.
#
# The actual method that is called is determined by calling
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 332d86b089..d57136dbb8 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -17,12 +17,6 @@ module AbstractController
self._view_paths ||= ActionView::PathSet.new
end
- # Initialize controller with nil formats.
- def initialize(*) #:nodoc:
- @_formats = nil
- super
- end
-
# An instance of a view class. The default view class is ActionView::Base
#
# The view class must have the following methods:
diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb
new file mode 100644
index 0000000000..6b7d2b1f34
--- /dev/null
+++ b/actionpack/lib/abstract_controller/url_for.rb
@@ -0,0 +1,156 @@
+module AbstractController
+ # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
+ # is also possible: an URL can be generated from one of your routing definitions.
+ # URL generation functionality is centralized in this module.
+ #
+ # See AbstractController::Routing and AbstractController::Resources for general
+ # information about routing and routes.rb.
+ #
+ # <b>Tip:</b> If you need to generate URLs from your models or some other place,
+ # then AbstractController::UrlFor is what you're looking for. Read on for
+ # an introduction.
+ #
+ # == URL generation from parameters
+ #
+ # As you may know, some functions - such as AbstractController::Base#url_for
+ # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
+ # of parameters. For example, you've probably had the chance to write code
+ # like this in one of your views:
+ #
+ # <%= link_to('Click here', :controller => 'users',
+ # :action => 'new', :message => 'Welcome!') %>
+ #
+ # #=> Generates a link to: /users/new?message=Welcome%21
+ #
+ # link_to, and all other functions that require URL generation functionality,
+ # actually use AbstractController::UrlFor under the hood. And in particular,
+ # they use the AbstractController::UrlFor#url_for method. One can generate
+ # the same path as the above example by using the following code:
+ #
+ # include UrlFor
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :only_path => true)
+ # # => "/users/new?message=Welcome%21"
+ #
+ # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
+ # information about the website hostname that your Rails app is serving. So if you
+ # want to include the hostname as well, then you must also pass the <tt>:host</tt>
+ # argument:
+ #
+ # include UrlFor
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :host => 'www.example.com') # Changed this.
+ # # => "http://www.example.com/users/new?message=Welcome%21"
+ #
+ # By default, all controllers and views have access to a special version of url_for,
+ # that already knows what the current hostname is. So if you use url_for in your
+ # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
+ # argument.
+ #
+ # For convenience reasons, mailers provide a shortcut for AbstractController::UrlFor#url_for.
+ # So within mailers, you only have to type 'url_for' instead of 'AbstractController::UrlFor#url_for'
+ # in full. However, mailers don't have hostname information, and what's why you'll still
+ # have to specify the <tt>:host</tt> argument when generating URLs in mailers.
+ #
+ #
+ # == URL generation for named routes
+ #
+ # UrlFor also allows one to access methods that have been auto-generated from
+ # named routes. For example, suppose that you have a 'users' resource in your
+ # <b>routes.rb</b>:
+ #
+ # map.resources :users
+ #
+ # This generates, among other things, the method <tt>users_path</tt>. By default,
+ # this method is accessible from your controllers, views and mailers. If you need
+ # to access this auto-generated method from other places (such as a model), then
+ # you can do that by including AbstractController::UrlFor in your class:
+ #
+ # class User < ActiveRecord::Base
+ # include AbstractController::UrlFor
+ #
+ # def base_uri
+ # user_path(self)
+ # end
+ # end
+ #
+ # User.find(1).base_uri # => "/users/1"
+ #
+ module UrlFor
+ extend ActiveSupport::Concern
+
+ included do
+ ActionController::Routing::Routes.install_helpers(self)
+ extlib_inheritable_accessor :default_url_options,
+ :instance_writer => false, :instance_reader => false
+ self.default_url_options ||= {}
+ end
+
+ # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
+ # the form of a hash, just like the one you would use for url_for directly. Example:
+ #
+ # def default_url_options(options)
+ # { :project => @project.active? ? @project.url_name : "unknown" }
+ # end
+ #
+ # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
+ # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
+ # by this method.
+ def default_url_options(options = nil)
+ self.class.default_url_options
+ end
+
+ def rewrite_options(options) #:nodoc:
+ if options.delete(:use_defaults) != false && (defaults = default_url_options(options))
+ defaults.merge(options)
+ else
+ options
+ end
+ end
+
+ # Generate a url based on the options provided, default_url_options and the
+ # routes defined in routes.rb. The following options are supported:
+ #
+ # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
+ # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
+ # * <tt>:host</tt> - Specifies the host the link should be targeted at.
+ # If <tt>:only_path</tt> is false, this option must be
+ # provided either explicitly, or via +default_url_options+.
+ # * <tt>:port</tt> - Optionally specify the port to connect to.
+ # * <tt>:anchor</tt> - An anchor name to be appended to the path.
+ # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
+ # +relative_url_root+ set in AbstractController::Base.relative_url_root.
+ # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
+ #
+ # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
+ # +url_for+ is forwarded to the Routes module.
+ #
+ # Examples:
+ #
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ def url_for(options = {})
+ options ||= {}
+ case options
+ when String
+ options
+ when Hash
+ _url_rewriter.rewrite(rewrite_options(options))
+ else
+ polymorphic_url(options)
+ end
+ end
+
+ protected
+
+ def _url_rewriter
+ ActionController::UrlRewriter
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index d66fc3fcc9..8bc2cc79d2 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -23,31 +23,32 @@ module ActionController
autoload :Helpers
autoload :HideActions
autoload :HttpAuthentication
- autoload :Logger
+ autoload :Instrumentation
autoload :MimeResponds
autoload :RackDelegation
autoload :Redirecting
- autoload :Rendering
autoload :Renderers
+ autoload :Rendering
autoload :RequestForgeryProtection
autoload :Rescue
autoload :Responder
autoload :SessionManagement
autoload :Streaming
+ autoload :Testing
autoload :UrlFor
autoload :Verification
end
- autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
- autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :Routing, 'action_controller/deprecated'
+ autoload :Dispatcher, 'action_controller/deprecated/dispatcher'
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
+ autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
+ autoload :Routing, 'action_controller/deprecated'
+ autoload :TestCase, 'action_controller/test_case'
eager_autoload do
autoload :RecordIdentifier
autoload :UrlRewriter
- autoload :UrlWriter, 'action_controller/url_rewriter'
# TODO: Don't autoload exceptions, setup explicit
# requires for files that need them
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index b23be66910..260e5da336 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -6,6 +6,8 @@ module ActionController
include AbstractController::Layouts
include ActionController::Helpers
+ helper :all # By default, all helpers should be included
+
include ActionController::HideActions
include ActionController::UrlFor
include ActionController::Redirecting
@@ -13,7 +15,6 @@ module ActionController
include ActionController::Renderers::All
include ActionController::ConditionalGet
include ActionController::RackDelegation
- include ActionController::Logger
include ActionController::Configuration
# Legacy modules
@@ -31,9 +32,13 @@ module ActionController
include ActionController::Streaming
include ActionController::HttpAuthentication::Basic::ControllerMethods
include ActionController::HttpAuthentication::Digest::ControllerMethods
- include ActionController::FilterParameterLogging
include ActionController::Translation
+ # Add instrumentations hooks at the bottom, to ensure they instrument
+ # all the methods properly.
+ include ActionController::Instrumentation
+ include ActionController::FilterParameterLogging
+
# TODO: Extract into its own module
# This should be moved together with other normalizing behavior
module ImplicitRender
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 69ed84da95..d784138ebe 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -60,17 +60,6 @@ module ActionController #:nodoc:
def cache_configured?
perform_caching && cache_store
end
-
- def log_event(name, before, after, instrumenter_id, payload)
- if name.to_s =~ /(read|write|cache|expire|exist)_(fragment|page)\??/
- key_or_path = payload[:key] || payload[:path]
- human_name = name.to_s.humanize
- duration = (after - before) * 1000
- logger.info("#{human_name} #{key_or_path.inspect} (%.1fms)" % duration)
- else
- super
- end
- end
end
def caching_allowed?
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index f569d0dd8b..00a7f034d3 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -36,8 +36,8 @@ module ActionController #:nodoc:
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
- if fragment_exist?(name,options)
- buffer.concat(read_fragment(name, options))
+ if fragment_exist?(name, options)
+ buffer.safe_concat(read_fragment(name, options))
else
pos = buffer.length
block.call
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
return content unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do
+ instrument_fragment_cache :write_fragment, key do
cache_store.write(key, content, options)
end
content
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
return unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do
+ instrument_fragment_cache :read_fragment, key do
cache_store.read(key, options)
end
end
@@ -74,7 +74,7 @@ module ActionController #:nodoc:
return unless cache_configured?
key = fragment_cache_key(key)
- ActiveSupport::Notifications.instrument(:exist_fragment?, :key => key) do
+ instrument_fragment_cache :exist_fragment?, key do
cache_store.exist?(key, options)
end
end
@@ -101,16 +101,18 @@ module ActionController #:nodoc:
key = fragment_cache_key(key) unless key.is_a?(Regexp)
message = nil
- ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do
+ instrument_fragment_cache :expire_fragment, key do
if key.is_a?(Regexp)
- message = "Expired fragments matching: #{key.source}"
cache_store.delete_matched(key, options)
else
- message = "Expired fragment: #{key}"
cache_store.delete(key, options)
end
end
end
+
+ def instrument_fragment_cache(name, key)
+ ActiveSupport::Notifications.instrument("action_controller.#{name}", :key => key){ yield }
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index d46f528c7e..5797eeebd6 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- ActiveSupport::Notifications.instrument(:expire_page, :path => path) do
+ instrument_page_cache :expire_page, path do
File.delete(path) if File.exist?(path)
end
end
@@ -75,7 +75,7 @@ module ActionController #:nodoc:
return unless perform_caching
path = page_cache_path(path)
- ActiveSupport::Notifications.instrument(:cache_page, :path => path) do
+ instrument_page_cache :write_page, path do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
end
@@ -107,6 +107,10 @@ module ActionController #:nodoc:
def page_cache_path(path)
page_cache_directory + page_cache_file(path)
end
+
+ def instrument_page_cache(name, path)
+ ActiveSupport::Notifications.instrument("action_controller.#{name}", :path => path){ yield }
+ end
end
# Expires the page that was cached with the +options+ as a key. Example:
diff --git a/actionpack/lib/action_controller/deprecated/dispatcher.rb b/actionpack/lib/action_controller/deprecated/dispatcher.rb
new file mode 100644
index 0000000000..3da3c8ce7d
--- /dev/null
+++ b/actionpack/lib/action_controller/deprecated/dispatcher.rb
@@ -0,0 +1,31 @@
+module ActionController
+ class Dispatcher
+ cattr_accessor :prepare_each_request
+ self.prepare_each_request = false
+
+ class << self
+ def before_dispatch(*args, &block)
+ ActiveSupport::Deprecation.warn "ActionController::Dispatcher.before_dispatch is deprecated. " <<
+ "Please use ActionDispatch::Callbacks.before instead.", caller
+ ActionDispatch::Callbacks.before(*args, &block)
+ end
+
+ def after_dispatch(*args, &block)
+ ActiveSupport::Deprecation.warn "ActionController::Dispatcher.after_dispatch is deprecated. " <<
+ "Please use ActionDispatch::Callbacks.after instead.", caller
+ ActionDispatch::Callbacks.after(*args, &block)
+ end
+
+ def to_prepare(*args, &block)
+ ActiveSupport::Deprecation.warn "ActionController::Dispatcher.to_prepare is deprecated. " <<
+ "Please use ActionDispatch::Callbacks.to_prepare instead.", caller
+ ActionDispatch::Callbacks.after(*args, &block)
+ end
+
+ def new
+ ActiveSupport::Deprecation.warn "ActionController::Dispatcher.new is deprecated, use Rails.application instead."
+ Rails.application
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb
deleted file mode 100644
index cf02757cf6..0000000000
--- a/actionpack/lib/action_controller/dispatch/dispatcher.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'active_support/core_ext/module/delegation'
-
-module ActionController
- # Dispatches requests to the appropriate controller and takes care of
- # reloading the app after each request when Dependencies.load? is true.
- class Dispatcher
- cattr_accessor :prepare_each_request
- self.prepare_each_request = false
-
- class << self
- def define_dispatcher_callbacks(cache_classes)
- unless cache_classes
- # Run prepare callbacks before every request in development mode
- self.prepare_each_request = true
-
- ActionDispatch::Callbacks.after_dispatch do
- # Cleanup the application before processing the current request.
- ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
- ActiveSupport::Dependencies.clear
- ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
- end
-
- ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
- end
-
- if defined?(ActiveRecord)
- to_prepare(:activerecord_instantiate_observers) do
- ActiveRecord::Base.instantiate_observers
- end
- end
-
- if Base.logger && Base.logger.respond_to?(:flush)
- after_dispatch do
- Base.logger.flush
- end
- end
-
- to_prepare do
- I18n.reload!
- end
- end
-
- delegate :to_prepare, :before_dispatch, :around_dispatch, :after_dispatch,
- :to => ActionDispatch::Callbacks
-
- def new
- # DEPRECATE Rails application fallback
- Rails.application
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index a90f798cd5..0e869e4e87 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -21,6 +21,8 @@ module ActionController
class << self
delegate :default_charset=, :to => "ActionDispatch::Response"
+ delegate :resources_path_names, :to => "ActionController::Routing::Routes"
+ delegate :resources_path_names=, :to => "ActionController::Routing::Routes"
end
# cattr_reader :protected_instance_variables
@@ -29,15 +31,7 @@ module ActionController
@variables_added @request_origin @url
@parent_controller @action_name
@before_filter_chain_aborted @_headers @_params
- @_flash @_response)
-
- # Indicates whether or not optimise the generated named
- # route helper methods
- cattr_accessor :optimise_named_routes
- self.optimise_named_routes = true
-
- cattr_accessor :resources_path_names
- self.resources_path_names = { :new => 'new', :edit => 'edit' }
+ @_response)
# Controls the resource action separator
cattr_accessor :resource_action_separator
diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
index 59e200396a..0b1e1ee6ab 100644
--- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
+++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
@@ -2,6 +2,8 @@ module ActionController
module FilterParameterLogging
extend ActiveSupport::Concern
+ INTERNAL_PARAMS = %w(controller action format _method only_path)
+
module ClassMethods
# Replace sensitive parameter data from the request log.
# Filters parameters that have any of the arguments as a substring.
@@ -48,27 +50,19 @@ module ActionController
filtered_params[key] = value
end
- filtered_params
+ filtered_params.except!(*INTERNAL_PARAMS)
end
protected :filter_parameters
end
-
- protected
-
- # Overwrite log_process_action to include parameters information.
- # If this method is invoked, it means logger is defined, so don't
- # worry with such scenario here.
- def log_process_action(controller) #:nodoc:
- params = controller.send(:filter_parameters, controller.request.params)
- logger.info " Parameters: #{params.inspect}" unless params.empty?
- super
- end
end
- INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
-
protected
+ def append_info_to_payload(payload)
+ super
+ payload[:params] = filter_parameters(request.params)
+ end
+
def filter_parameters(params)
params.dup.except!(*INTERNAL_PARAMS)
end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 25e25940a7..bd768b634e 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -1,187 +1,14 @@
module ActionController #:nodoc:
- # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
- # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
- # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
- # then expose the flash to its template. Actually, that exposure is automatically done. Example:
- #
- # class PostsController < ActionController::Base
- # def create
- # # save post
- # flash[:notice] = "Successfully created post"
- # redirect_to @post
- # end
- #
- # def show
- # # doesn't need to assign the flash notice to the template, that's done automatically
- # end
- # end
- #
- # show.html.erb
- # <% if flash[:notice] %>
- # <div class="notice"><%= flash[:notice] %></div>
- # <% end %>
- #
- # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
- # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
- #
- # See docs on the FlashHash class for more details about the flash.
module Flash
extend ActiveSupport::Concern
included do
+ delegate :flash, :to => :request
+ delegate :alert, :notice, :to => "request.flash"
helper_method :alert, :notice
end
- class FlashNow #:nodoc:
- def initialize(flash)
- @flash = flash
- end
-
- def []=(k, v)
- @flash[k] = v
- @flash.discard(k)
- v
- end
-
- def [](k)
- @flash[k]
- end
- end
-
- class FlashHash < Hash
- def initialize #:nodoc:
- super
- @used = Set.new
- end
-
- def []=(k, v) #:nodoc:
- keep(k)
- super
- end
-
- def update(h) #:nodoc:
- h.keys.each { |k| keep(k) }
- super
- end
-
- alias :merge! :update
-
- def replace(h) #:nodoc:
- @used = Set.new
- super
- end
-
- # Sets a flash that will not be available to the next action, only to the current.
- #
- # flash.now[:message] = "Hello current action"
- #
- # This method enables you to use the flash as a central messaging system in your app.
- # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
- # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
- # vanish when the current action is done.
- #
- # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
- def now
- FlashNow.new(self)
- end
-
- # Keeps either the entire current flash or a specific flash entry available for the next action:
- #
- # flash.keep # keeps the entire flash
- # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
- def keep(k = nil)
- use(k, false)
- end
-
- # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
- #
- # flash.discard # discard the entire flash at the end of the current action
- # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
- def discard(k = nil)
- use(k)
- end
-
- # Mark for removal entries that were kept, and delete unkept ones.
- #
- # This method is called automatically by filters, so you generally don't need to care about it.
- def sweep #:nodoc:
- keys.each do |k|
- unless @used.include?(k)
- @used << k
- else
- delete(k)
- @used.delete(k)
- end
- end
-
- # clean up after keys that could have been left over by calling reject! or shift on the flash
- (@used - keys).each{ |k| @used.delete(k) }
- end
-
- def store(session)
- return if self.empty?
- session["flash"] = self
- end
-
- private
- # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
- # use() # marks the entire flash as used
- # use('msg') # marks the "msg" entry as used
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
- # if no key is passed.
- def use(key = nil, used = true)
- Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
- return key ? self[key] : self
- end
- end
-
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
- # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
- # to put a new one.
- def flash #:doc:
- unless @_flash
- @_flash = session["flash"] || FlashHash.new
- @_flash.sweep
- end
-
- @_flash
- end
-
- # Convenience accessor for flash[:alert]
- def alert
- flash[:alert]
- end
-
- # Convenience accessor for flash[:alert]=
- def alert=(message)
- flash[:alert] = message
- end
-
- # Convenience accessor for flash[:notice]
- def notice
- flash[:notice]
- end
-
- # Convenience accessor for flash[:notice]=
- def notice=(message)
- flash[:notice] = message
- end
-
protected
- def process_action(method_name)
- @_flash = nil
- super
- @_flash.store(session) if @_flash
- @_flash = nil
- end
-
- def reset_session
- super
- @_flash = nil
- end
-
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
if alert = response_status_and_flash.delete(:alert)
flash[:alert] = alert
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index c82d9cf369..37be8b3999 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -1,6 +1,7 @@
module ActionController
module Head
- include UrlFor
+ extend ActiveSupport::Concern
+ include ActionController::UrlFor
# Return a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
new file mode 100644
index 0000000000..7222b7b2fa
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -0,0 +1,102 @@
+require 'abstract_controller/logger'
+
+module ActionController
+ # Adds instrumentation to several ends in ActionController::Base. It also provides
+ # some hooks related with process_action, this allows an ORM like ActiveRecord
+ # and/or DataMapper to plug in ActionController and show related information.
+ #
+ # Check ActiveRecord::Railties::ControllerRuntime for an example.
+ module Instrumentation
+ extend ActiveSupport::Concern
+
+ included do
+ include AbstractController::Logger
+ end
+
+ attr_internal :view_runtime
+
+ def process_action(action, *args)
+ ActiveSupport::Notifications.instrument("action_controller.process_action") do |payload|
+ result = super
+ payload[:controller] = self.class.name
+ payload[:action] = self.action_name
+ payload[:formats] = request.formats.map(&:to_s)
+ payload[:remote_ip] = request.remote_ip
+ payload[:method] = request.method
+ payload[:status] = response.status
+ payload[:request_uri] = request.request_uri rescue "unknown"
+ append_info_to_payload(payload)
+ result
+ end
+ end
+
+ def render(*args, &block)
+ if logger
+ render_output = nil
+
+ self.view_runtime = cleanup_view_runtime do
+ Benchmark.ms { render_output = super }
+ end
+
+ render_output
+ else
+ super
+ end
+ end
+
+ def send_file(path, options={})
+ ActiveSupport::Notifications.instrument("action_controller.send_file",
+ options.merge(:path => path)) do
+ super
+ end
+ end
+
+ def send_data(data, options = {})
+ ActiveSupport::Notifications.instrument("action_controller.send_data", options) do
+ super
+ end
+ end
+
+ def redirect_to(*args)
+ ActiveSupport::Notifications.instrument("action_controller.redirect_to") do |payload|
+ result = super
+ payload[:status] = self.status
+ payload[:location] = self.location
+ result
+ end
+ end
+
+ protected
+
+ # A hook which allows you to clean up any time taken into account in
+ # views wrongly, like database querying time.
+ #
+ # def cleanup_view_runtime
+ # super - time_taken_in_something_expensive
+ # end
+ #
+ # :api: plugin
+ def cleanup_view_runtime #:nodoc:
+ yield
+ end
+
+ # Everytime after an action is processed, this method is invoked
+ # with the payload, so you can add more information.
+ # :api: plugin
+ def append_info_to_payload(payload) #:nodoc:
+ payload[:view_runtime] = view_runtime
+ end
+
+ module ClassMethods
+ # A hook which allows other frameworks to log what happened during
+ # controller process action. This method should return an array
+ # with the messages to be added.
+ # :api: plugin
+ def log_process_action(payload) #:nodoc:
+ messages, view_runtime = [], payload[:view_runtime]
+ messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
+ messages
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/logger.rb b/actionpack/lib/action_controller/metal/logger.rb
deleted file mode 100644
index 4f4370e5f0..0000000000
--- a/actionpack/lib/action_controller/metal/logger.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'abstract_controller/logger'
-
-module ActionController
- # Adds instrumentation to <tt>process_action</tt> and a <tt>log_event</tt> method
- # responsible to log events from ActiveSupport::Notifications. This module handles
- # :process_action and :render_template events but allows any other module to hook
- # into log_event and provide its own logging facilities (as in ActionController::Caching).
- module Logger
- extend ActiveSupport::Concern
-
- included do
- include AbstractController::Logger
- end
-
- attr_internal :view_runtime
-
- def process_action(action)
- ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do
- super
- end
- end
-
- def render(*args, &block)
- if logger
- render_output = nil
-
- self.view_runtime = cleanup_view_runtime do
- Benchmark.ms { render_output = super }
- end
-
- render_output
- else
- super
- end
- end
-
- # If you want to remove any time taken into account in :view_runtime
- # wrongly, you can do it here:
- #
- # def cleanup_view_runtime
- # super - time_taken_in_something_expensive
- # end
- #
- # :api: plugin
- def cleanup_view_runtime #:nodoc:
- yield
- end
-
- module ClassMethods
- # This is the hook invoked by ActiveSupport::Notifications.subscribe.
- # If you need to log any event, overwrite the method and do it here.
- def log_event(name, before, after, instrumenter_id, payload) #:nodoc:
- if name == :process_action
- duration = [(after - before) * 1000, 0.01].max
- controller = payload[:controller]
- request = controller.request
-
- logger.info "\n\nProcessed #{controller.class.name}##{payload[:action]} " \
- "to #{request.formats} (for #{request.remote_ip} at #{before.to_s(:db)}) " \
- "[#{request.method.to_s.upcase}]"
-
- log_process_action(controller)
-
- message = "Completed in %.0fms" % duration
- message << " | #{controller.response.status}"
- message << " [#{request.request_uri rescue "unknown"}]"
-
- logger.info(message)
- elsif name == :render_template
- # TODO Make render_template logging work if you are using just ActionView
- duration = (after - before) * 1000
- message = "Rendered #{payload[:identifier]}"
- message << " within #{payload[:layout]}" if payload[:layout]
- message << (" (%.1fms)" % duration)
- logger.info(message)
- end
- end
-
- protected
-
- # A hook which allows logging what happened during controller process action.
- # :api: plugin
- def log_process_action(controller) #:nodoc:
- view_runtime = controller.send :view_runtime
- logger.info(" View runtime: %.1fms" % view_runtime.to_f) if view_runtime
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 468c5f4fae..4c02677729 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -215,7 +215,10 @@ module ActionController #:nodoc:
# a proc to it.
#
def respond_with(*resources, &block)
- if response = retrieve_response_from_mimes([], &block)
+ raise "In order to use respond_with, first you need to declare the formats your " <<
+ "controller responds to in the class level" if mimes_for_respond_to.empty?
+
+ if response = retrieve_response_from_mimes(&block)
options = resources.extract_options!
options.merge!(:default_response => response)
(options.delete(:responder) || responder).call(self, resources, options)
@@ -246,9 +249,9 @@ module ActionController #:nodoc:
# Collects mimes and return the response for the negotiated format. Returns
# nil if :not_acceptable was sent to the client.
#
- def retrieve_response_from_mimes(mimes, &block)
+ def retrieve_response_from_mimes(mimes=nil, &block)
collector = Collector.new { default_render }
- mimes = collect_mimes_from_class_level if mimes.empty?
+ mimes ||= collect_mimes_from_class_level
mimes.each { |mime| collector.send(mime) }
block.call(collector) if block_given?
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 7a2f9a6fc5..faf0589fd2 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -9,7 +9,9 @@ module ActionController
module Redirecting
extend ActiveSupport::Concern
+
include AbstractController::Logger
+ include ActionController::UrlFor
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
#
@@ -55,8 +57,6 @@ module ActionController
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
-
- logger.info("Redirected to #{location}") if logger && logger.info?
end
private
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 288b5d7c99..8f03b8bb17 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -88,13 +88,11 @@ module ActionController #:nodoc:
@performed_render = false
if options[:x_sendfile]
- logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
head options[:status], X_SENDFILE_HEADER => path
else
if options[:stream]
# TODO : Make render :text => proc {} work with the new base
render :status => options[:status], :text => Proc.new { |response, output|
- logger.info "Streaming file #{path}" unless logger.nil?
len = options[:buffer_size] || 4096
File.open(path, 'rb') do |file|
while buf = file.read(len)
@@ -103,7 +101,6 @@ module ActionController #:nodoc:
end
}
else
- logger.info "Sending file #{path}" unless logger.nil?
File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
end
end
@@ -141,7 +138,6 @@ module ActionController #:nodoc:
# data to the browser, then use <tt>render :text => proc { ... }</tt>
# instead. See ActionController::Base#render for more information.
def send_data(data, options = {}) #:doc:
- logger.info "Sending data #{options[:filename]}" if logger
send_file_headers! options.merge(:length => data.bytesize)
render :status => options[:status], :text => data
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 8c3810ebcb..73feacb872 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -2,40 +2,14 @@ module ActionController
module UrlFor
extend ActiveSupport::Concern
- include RackDelegation
+ include AbstractController::UrlFor
+ include ActionController::RackDelegation
- # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
- # the form of a hash, just like the one you would use for url_for directly. Example:
- #
- # def default_url_options(options)
- # { :project => @project.active? ? @project.url_name : "unknown" }
- # end
- #
- # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
- # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
- # by this method.
- def default_url_options(options = nil)
- end
-
- def rewrite_options(options) #:nodoc:
- if defaults = default_url_options(options)
- defaults.merge(options)
- else
- options
- end
- end
+ protected
- def url_for(options = {})
- options ||= {}
- case options
- when String
- options
- when Hash
- @url ||= UrlRewriter.new(request, params)
- @url.rewrite(rewrite_options(options))
- else
- polymorphic_url(options)
- end
+ def _url_rewriter
+ return ActionController::UrlRewriter unless request
+ @_url_rewriter ||= ActionController::UrlRewriter.new(request, params)
end
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f861d12905..741101a210 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -5,6 +5,9 @@ module ActionController
class Railtie < Rails::Railtie
plugin_name :action_controller
+ require "action_controller/railties/subscriber"
+ subscriber ActionController::Railties::Subscriber.new
+
initializer "action_controller.set_configs" do |app|
app.config.action_controller.each do |k,v|
ActionController::Base.send "#{k}=", v
@@ -23,26 +26,6 @@ module ActionController
app.reload_routes!
end
- # Include middleware to serve up static assets
- initializer "action_controller.initialize_static_server" do |app|
- if app.config.serve_static_assets
- app.config.middleware.use(ActionDispatch::Static, Rails.public_path)
- end
- end
-
- initializer "action_controller.initialize_middleware_stack" do |app|
- middleware = app.config.middleware
- middleware.use(::Rack::Lock, :if => lambda { ActionController::Base.allow_concurrency })
- middleware.use(::Rack::Runtime)
- middleware.use(ActionDispatch::ShowExceptions, lambda { ActionController::Base.consider_all_requests_local })
- middleware.use(ActionDispatch::Callbacks, lambda { ActionController::Dispatcher.prepare_each_request })
- middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options })
- middleware.use(ActionDispatch::ParamsParser)
- middleware.use(::Rack::MethodOverride)
- middleware.use(::Rack::Head)
- middleware.use(ActionDispatch::StringCoercion)
- end
-
initializer "action_controller.initialize_framework_caches" do
ActionController::Base.cache_store ||= RAILS_CACHE
end
@@ -57,19 +40,41 @@ module ActionController
ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank?
end
+ class MetalMiddlewareBuilder
+ def initialize(metals)
+ @metals = metals
+ end
+
+ def new(app)
+ ActionDispatch::Cascade.new(@metals, app)
+ end
+
+ def name
+ ActionDispatch::Cascade.name
+ end
+ alias_method :to_s, :name
+ end
+
initializer "action_controller.initialize_metal" do |app|
- Rails::Rack::Metal.requested_metals = app.config.metals
+ metal_root = "#{Rails.root}/app/metal"
+ load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"]
- app.config.middleware.insert_before(:"ActionDispatch::ParamsParser",
- Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)
+ metals = load_list.map { |metal|
+ metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb')
+ require_dependency metal
+ metal.camelize.constantize
+ }.compact
+
+ middleware = MetalMiddlewareBuilder.new(metals)
+ app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware)
end
- # # Prepare dispatcher callbacks and run 'prepare' callbacks
+ # Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_controller.prepare_dispatcher" do |app|
# TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
+ # Notice that at this point, ActionDispatch::Callbacks were already loaded.
require 'rails/dispatcher'
-
- Dispatcher.define_dispatcher_callbacks(app.config.cache_classes)
+ ActionController::Dispatcher.prepare_each_request = true unless app.config.cache_classes
unless app.config.cache_classes
# Setup dev mode route reloading
@@ -80,15 +85,7 @@ module ActionController
app.reload_routes!
end
end
- ActionDispatch::Callbacks.before_dispatch { |callbacks| reload_routes.call }
- end
- end
-
- initializer "action_controller.notifications" do |app|
- require 'active_support/notifications'
-
- ActiveSupport::Notifications.subscribe do |*args|
- ActionController::Base.log_event(*args) if ActionController::Base.logger
+ ActionDispatch::Callbacks.before { |callbacks| reload_routes.call }
end
end
diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/subscriber.rb
new file mode 100644
index 0000000000..a9f5d16c58
--- /dev/null
+++ b/actionpack/lib/action_controller/railties/subscriber.rb
@@ -0,0 +1,60 @@
+module ActionController
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def process_action(event)
+ payload = event.payload
+
+ info "\nProcessed #{payload[:controller]}##{payload[:action]} " \
+ "to #{payload[:formats].join(', ')} (for #{payload[:remote_ip]} at #{event.time.to_s(:db)}) " \
+ "[#{payload[:method].to_s.upcase}]"
+
+ info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank?
+
+ additions = ActionController::Base.log_process_action(payload)
+
+ message = "Completed in %.0fms" % event.duration
+ message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << " | #{payload[:status]} [#{payload[:request_uri]}]\n\n"
+
+ info(message)
+ end
+
+ def send_file(event)
+ message = if event.payload[:x_sendfile]
+ header = ActionController::Streaming::X_SENDFILE_HEADER
+ "Sent #{header} header %s"
+ elsif event.payload[:stream]
+ "Streamed file %s"
+ else
+ "Sent file %s"
+ end
+
+ message << " (%.1fms)"
+ info(message % [event.payload[:path], event.duration])
+ end
+
+ def redirect_to(event)
+ info "Redirected to #{event.payload[:location]}"
+ end
+
+ def send_data(event)
+ info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration])
+ end
+
+ %w(write_fragment read_fragment exist_fragment?
+ expire_fragment expire_page write_page).each do |method|
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{method}(event)
+ key_or_path = event.payload[:key] || event.payload[:path]
+ human_name = #{method.to_s.humanize.inspect}
+ info("\#{human_name} \#{key_or_path} (%.1fms)" % event.duration)
+ end
+ METHOD
+ end
+
+ def logger
+ ActionController::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 398ea52495..14557ca782 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,6 +1,5 @@
-require 'active_support/test_case'
require 'rack/session/abstract/id'
-require 'action_controller/metal/testing'
+require 'action_view/test_case'
module ActionController
class TestRequest < ActionDispatch::TestRequest #:nodoc:
@@ -240,13 +239,15 @@ module ActionController
@request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
@request.session = ActionController::TestSession.new(session) unless session.nil?
- @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
+ @request.session["flash"] = @request.flash.update(flash || {})
+ @request.session["flash"].sweep
@controller.request = @request
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
Base.class_eval { include Testing }
@controller.process_with_new_base_test(@request, @response)
+ @request.session.delete('flash') if @request.session['flash'].blank?
@response
end
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index 52b66c9303..933a1fa8f9 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -1,153 +1,21 @@
-module ActionController
- # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
- # is also possible: an URL can be generated from one of your routing definitions.
- # URL generation functionality is centralized in this module.
- #
- # See ActionController::Routing and ActionController::Resources for general
- # information about routing and routes.rb.
- #
- # <b>Tip:</b> If you need to generate URLs from your models or some other place,
- # then ActionController::UrlWriter is what you're looking for. Read on for
- # an introduction.
- #
- # == URL generation from parameters
- #
- # As you may know, some functions - such as ActionController::Base#url_for
- # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
- # of parameters. For example, you've probably had the chance to write code
- # like this in one of your views:
- #
- # <%= link_to('Click here', :controller => 'users',
- # :action => 'new', :message => 'Welcome!') %>
- #
- # #=> Generates a link to: /users/new?message=Welcome%21
- #
- # link_to, and all other functions that require URL generation functionality,
- # actually use ActionController::UrlWriter under the hood. And in particular,
- # they use the ActionController::UrlWriter#url_for method. One can generate
- # the same path as the above example by using the following code:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :only_path => true)
- # # => "/users/new?message=Welcome%21"
- #
- # Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
- # information about the website hostname that your Rails app is serving. So if you
- # want to include the hostname as well, then you must also pass the <tt>:host</tt>
- # argument:
- #
- # include UrlWriter
- # url_for(:controller => 'users',
- # :action => 'new',
- # :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
- # # => "http://www.example.com/users/new?message=Welcome%21"
- #
- # By default, all controllers and views have access to a special version of url_for,
- # that already knows what the current hostname is. So if you use url_for in your
- # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
- # argument.
- #
- # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
- # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
- # have to specify the <tt>:host</tt> argument when generating URLs in mailers.
- #
- #
- # == URL generation for named routes
- #
- # UrlWriter also allows one to access methods that have been auto-generated from
- # named routes. For example, suppose that you have a 'users' resource in your
- # <b>routes.rb</b>:
- #
- # map.resources :users
- #
- # This generates, among other things, the method <tt>users_path</tt>. By default,
- # this method is accessible from your controllers, views and mailers. If you need
- # to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlWriter in your class:
- #
- # class User < ActiveRecord::Base
- # include ActionController::UrlWriter
- #
- # def base_uri
- # user_path(self)
- # end
- # end
- #
- # User.find(1).base_uri # => "/users/1"
- module UrlWriter
- def self.included(base) #:nodoc:
- ActionController::Routing::Routes.install_helpers(base)
- base.mattr_accessor :default_url_options
-
- # The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided.
- base.default_url_options ||= {}
- end
-
- # Generate a url based on the options provided, default_url_options and the
- # routes defined in routes.rb. The following options are supported:
- #
- # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
- # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
- # * <tt>:host</tt> - Specifies the host the link should be targeted at.
- # If <tt>:only_path</tt> is false, this option must be
- # provided either explicitly, or via +default_url_options+.
- # * <tt>:port</tt> - Optionally specify the port to connect to.
- # * <tt>:anchor</tt> - An anchor name to be appended to the path.
- # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::Base.relative_url_root.
- # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
- #
- # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
- # +url_for+ is forwarded to the Routes module.
- #
- # Examples:
- #
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
- def url_for(options)
- options = self.class.default_url_options.merge(options)
-
- url = ''
-
- unless options.delete(:only_path)
- url << (options.delete(:protocol) || 'http')
- url << '://' unless url.match("://")
-
- raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
-
- url << options.delete(:host)
- url << ":#{options.delete(:port)}" if options.key?(:port)
- else
- # Delete the unused options to prevent their appearance in the query string.
- [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
- end
- trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
- url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
- generated = Routing::Routes.generate(options, {})
- url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
- url << anchor if anchor
-
- url
- end
- end
+require 'active_support/core_ext/hash/except'
+module ActionController
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
class UrlRewriter #:nodoc:
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
+
def initialize(request, parameters)
@request, @parameters = request, parameters
end
def rewrite(options = {})
- rewrite_url(options)
+ options[:host] ||= @request.host_with_port
+ options[:protocol] ||= @request.protocol
+
+ self.class.rewrite(options, @request.symbolized_path_parameters) do |options|
+ process_path_options(options)
+ end
end
def to_str
@@ -156,49 +24,53 @@ module ActionController
alias_method :to_s, :to_str
- private
- # Given a path and options, returns a rewritten URL string
- def rewrite_url(options)
- rewritten_url = ""
-
- unless options[:only_path]
- rewritten_url << (options[:protocol] || @request.protocol)
- rewritten_url << "://" unless rewritten_url.match("://")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << (options[:host] || @request.host_with_port)
- rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
- end
-
- path = rewrite_path(options)
- rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
-
- rewritten_url
+ def self.rewrite(options, path_segments=nil)
+ rewritten_url = ""
+
+ unless options[:only_path]
+ rewritten_url << (options[:protocol] || "http")
+ rewritten_url << "://" unless rewritten_url.match("://")
+ rewritten_url << rewrite_authentication(options)
+
+ raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
+
+ rewritten_url << options[:host]
+ rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
- # Given a Hash of options, generates a route
- def rewrite_path(options)
- options = options.symbolize_keys
- options.update(options[:params].symbolize_keys) if options[:params]
+ path_options = options.except(*RESERVED_OPTIONS)
+ path_options = yield(path_options) if block_given?
+ path = Routing::Routes.generate(path_options, path_segments || {})
+
+ rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
- if (overwrite = options.delete(:overwrite_params))
- options.update(@parameters.symbolize_keys)
- options.update(overwrite.symbolize_keys)
- end
+ rewritten_url
+ end
- RESERVED_OPTIONS.each { |k| options.delete(k) }
+ protected
- # Generates the query string, too
- Routing::Routes.generate(options, @request.symbolized_path_parameters)
+ def self.rewrite_authentication(options)
+ if options[:user] && options[:password]
+ "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
+ else
+ ""
end
+ end
+
+ # Given a Hash of options, generates a route
+ def process_path_options(options)
+ options = options.symbolize_keys
+ options.update(options[:params].symbolize_keys) if options[:params]
- def rewrite_authentication(options)
- if options[:user] && options[:password]
- "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@"
- else
- ""
- end
+ if (overwrite = options.delete(:overwrite_params))
+ options.update(@parameters.symbolize_keys)
+ options.update(overwrite.symbolize_keys)
end
+
+ options
+ end
+
end
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 1e87a016f9..7b44212310 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -43,18 +43,27 @@ module ActionDispatch
autoload_under 'middleware' do
autoload :Callbacks
autoload :Cascade
+ autoload :Flash
+ autoload :Head
autoload :ParamsParser
autoload :Rescue
autoload :ShowExceptions
autoload :Static
- autoload :StringCoercion
end
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
module Http
- autoload :Headers, 'action_dispatch/http/headers'
+ extend ActiveSupport::Autoload
+
+ autoload :Cache
+ autoload :Headers
+ autoload :MimeNegotiation
+ autoload :Parameters
+ autoload :Upload
+ autoload :UploadedFile, 'action_dispatch/http/upload'
+ autoload :URL
end
module Session
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
new file mode 100644
index 0000000000..428e62dc6b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -0,0 +1,123 @@
+module ActionDispatch
+ module Http
+ module Cache
+ module Request
+ def if_modified_since
+ if since = env['HTTP_IF_MODIFIED_SINCE']
+ Time.rfc2822(since) rescue nil
+ end
+ end
+
+ def if_none_match
+ env['HTTP_IF_NONE_MATCH']
+ end
+
+ def not_modified?(modified_at)
+ if_modified_since && modified_at && if_modified_since >= modified_at
+ end
+
+ def etag_matches?(etag)
+ if_none_match && if_none_match == etag
+ end
+
+ # Check response freshness (Last-Modified and ETag) against request
+ # If-Modified-Since and If-None-Match conditions. If both headers are
+ # supplied, both must match, or the request is not considered fresh.
+ def fresh?(response)
+ last_modified = if_modified_since
+ etag = if_none_match
+
+ return false unless last_modified || etag
+
+ success = true
+ success &&= not_modified?(response.last_modified) if last_modified
+ success &&= etag_matches?(response.etag) if etag
+ success
+ end
+ end
+
+ module Response
+ def cache_control
+ @cache_control ||= {}
+ end
+
+ def last_modified
+ if last = headers['Last-Modified']
+ Time.httpdate(last)
+ end
+ end
+
+ def last_modified?
+ headers.include?('Last-Modified')
+ end
+
+ def last_modified=(utc_time)
+ headers['Last-Modified'] = utc_time.httpdate
+ end
+
+ def etag
+ @etag
+ end
+
+ def etag?
+ @etag
+ end
+
+ def etag=(etag)
+ key = ActiveSupport::Cache.expand_cache_key(etag)
+ @etag = %("#{Digest::MD5.hexdigest(key)}")
+ end
+
+ private
+
+ def handle_conditional_get!
+ if etag? || last_modified? || !@cache_control.empty?
+ set_conditional_cache_control!
+ elsif nonempty_ok_response?
+ self.etag = @body
+
+ if request && request.etag_matches?(etag)
+ self.status = 304
+ self.body = []
+ end
+
+ set_conditional_cache_control!
+ else
+ headers["Cache-Control"] = "no-cache"
+ end
+ end
+
+ def nonempty_ok_response?
+ @status == 200 && string_body?
+ end
+
+ def string_body?
+ !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
+ end
+
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+
+ def set_conditional_cache_control!
+ control = @cache_control
+
+ if control.empty?
+ headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ elsif @cache_control[:no_cache]
+ headers["Cache-Control"] = "no-cache"
+ else
+ extras = control[:extras]
+ max_age = control[:max_age]
+
+ options = []
+ options << "max-age=#{max_age.to_i}" if max_age
+ options << (control[:public] ? "public" : "private")
+ options << "must-revalidate" if control[:must_revalidate]
+ options.concat(extras) if extras
+
+ headers["Cache-Control"] = options.join(", ")
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
new file mode 100644
index 0000000000..40617e239a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -0,0 +1,101 @@
+module ActionDispatch
+ module Http
+ module MimeNegotiation
+ # The MIME type of the HTTP request, such as Mime::XML.
+ #
+ # For backward compatibility, the post \format is extracted from the
+ # X-Post-Data-Format HTTP header if present.
+ def content_type
+ @env["action_dispatch.request.content_type"] ||= begin
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
+ end
+ end
+
+ # Returns the accepted MIME type for the request.
+ def accepts
+ @env["action_dispatch.request.accepts"] ||= begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
+
+ if header.empty?
+ [content_type]
+ else
+ Mime::Type.parse(header)
+ end
+ end
+ end
+
+ # Returns the Mime type for the \format used in the request.
+ #
+ # GET /posts/5.xml | request.format => Mime::XML
+ # GET /posts/5.xhtml | request.format => Mime::HTML
+ # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
+ #
+ def format(view_path = [])
+ formats.first
+ end
+
+ def formats
+ accept = @env['HTTP_ACCEPT']
+
+ @env["action_dispatch.request.formats"] ||=
+ if parameters[:format]
+ Array(Mime[parameters[:format]])
+ elsif xhr? || (accept && !accept.include?(?,))
+ accepts
+ else
+ [Mime::HTML]
+ end
+ end
+
+ # Sets the \format by string extension, which can be used to force custom formats
+ # that are not controlled by the extension.
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :adjust_format_for_iphone
+ #
+ # private
+ # def adjust_format_for_iphone
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
+ # end
+ # end
+ def format=(extension)
+ parameters[:format] = extension.to_s
+ @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
+ end
+
+ # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
+ # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
+ # otherwise.
+ def template_format
+ parameter_format = parameters[:format]
+
+ if parameter_format
+ parameter_format
+ elsif xhr?
+ :js
+ else
+ :html
+ end
+ end
+
+ # Receives an array of mimes and return the first user sent mime that
+ # matches the order array.
+ #
+ def negotiate_mime(order)
+ formats.each do |priority|
+ if priority == Mime::ALL
+ return order.first
+ elsif order.include?(priority)
+ return priority
+ end
+ end
+
+ order.include?(Mime::ALL) ? formats.first : nil
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
new file mode 100644
index 0000000000..97546d5f93
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -0,0 +1,50 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActionDispatch
+ module Http
+ module Parameters
+ # Returns both GET and POST \parameters in a single hash.
+ def parameters
+ @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
+ end
+ alias :params :parameters
+
+ def path_parameters=(parameters) #:nodoc:
+ @env.delete("action_dispatch.request.symbolized_path_parameters")
+ @env.delete("action_dispatch.request.parameters")
+ @env["action_dispatch.request.path_parameters"] = parameters
+ end
+
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
+ def symbolized_path_parameters
+ @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
+ end
+
+ # Returns a hash with the \parameters used to form the \path of the request.
+ # Returned hash keys are strings:
+ #
+ # {'action' => 'my_action', 'controller' => 'my_controller'}
+ #
+ # See <tt>symbolized_path_parameters</tt> for symbolized keys.
+ def path_parameters
+ @env["action_dispatch.request.path_parameters"] ||= {}
+ end
+
+ private
+
+ # Convert nested Hashs to HashWithIndifferentAccess
+ def normalize_parameters(value)
+ case value
+ when Hash
+ h = {}
+ value.each { |k, v| h[k] = normalize_parameters(v) }
+ h.with_indifferent_access
+ when Array
+ value.map { |e| normalize_parameters(e) }
+ else
+ value
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 6e8a5dcb8a..187ce7c15d 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -2,14 +2,17 @@ require 'tempfile'
require 'stringio'
require 'strscan'
-require 'active_support/memoizable'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'action_dispatch/http/headers'
module ActionDispatch
class Request < Rack::Request
+ include ActionDispatch::Http::Cache::Request
+ include ActionDispatch::Http::MimeNegotiation
+ include ActionDispatch::Http::Parameters
+ include ActionDispatch::Http::Upload
+ include ActionDispatch::Http::URL
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -19,9 +22,11 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env|
- define_method(env.sub(/^HTTP_/n, '').downcase) do
- @env[env]
- end
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{env.sub(/^HTTP_/n, '').downcase}
+ @env["#{env}"]
+ end
+ METHOD
end
def key?(key)
@@ -35,7 +40,8 @@ module ActionDispatch
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
# constant above, an UnknownHttpMethod exception is raised.
def request_method
- HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
+ method = env["rack.methodoverride.original_method"] || env["REQUEST_METHOD"]
+ HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
end
# Returns the HTTP request \method used for action processing as a
@@ -43,7 +49,8 @@ module ActionDispatch
# method returns <tt>:get</tt> for a HEAD request because the two are
# functionally equivalent from the application's perspective.)
def method
- request_method == :head ? :get : request_method
+ method = env["REQUEST_METHOD"]
+ HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
end
# Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>.
@@ -53,17 +60,17 @@ module ActionDispatch
# Is this a POST request? Equivalent to <tt>request.method == :post</tt>.
def post?
- request_method == :post
+ method == :post
end
# Is this a PUT request? Equivalent to <tt>request.method == :put</tt>.
def put?
- request_method == :put
+ method == :put
end
# Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>.
def delete?
- request_method == :delete
+ method == :delete
end
# Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>,
@@ -79,25 +86,6 @@ module ActionDispatch
Http::Headers.new(@env)
end
- # Returns the content length of the request as an integer.
- def content_length
- super.to_i
- end
-
- # The MIME type of the HTTP request, such as Mime::XML.
- #
- # For backward compatibility, the post \format is extracted from the
- # X-Post-Data-Format HTTP header if present.
- def content_type
- @env["action_dispatch.request.content_type"] ||= begin
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
- Mime::Type.lookup($1.strip.downcase)
- else
- nil
- end
- end
- end
-
def forgery_whitelisted?
method == :get || xhr? || content_type.nil? || !content_type.verify_request?
end
@@ -106,104 +94,9 @@ module ActionDispatch
content_type.to_s
end
- # Returns the accepted MIME type for the request.
- def accepts
- @env["action_dispatch.request.accepts"] ||= begin
- header = @env['HTTP_ACCEPT'].to_s.strip
-
- if header.empty?
- [content_type]
- else
- Mime::Type.parse(header)
- end
- end
- end
-
- def if_modified_since
- if since = env['HTTP_IF_MODIFIED_SINCE']
- Time.rfc2822(since) rescue nil
- end
- end
-
- def if_none_match
- env['HTTP_IF_NONE_MATCH']
- end
-
- def not_modified?(modified_at)
- if_modified_since && modified_at && if_modified_since >= modified_at
- end
-
- def etag_matches?(etag)
- if_none_match && if_none_match == etag
- end
-
- # Check response freshness (Last-Modified and ETag) against request
- # If-Modified-Since and If-None-Match conditions. If both headers are
- # supplied, both must match, or the request is not considered fresh.
- def fresh?(response)
- last_modified = if_modified_since
- etag = if_none_match
-
- return false unless last_modified || etag
-
- success = true
- success &&= not_modified?(response.last_modified) if last_modified
- success &&= etag_matches?(response.etag) if etag
- success
- end
-
- # Returns the Mime type for the \format used in the request.
- #
- # GET /posts/5.xml | request.format => Mime::XML
- # GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
- #
- def format(view_path = [])
- formats.first
- end
-
- def formats
- accept = @env['HTTP_ACCEPT']
-
- @env["action_dispatch.request.formats"] ||=
- if parameters[:format]
- Array.wrap(Mime[parameters[:format]])
- elsif xhr? || (accept && !accept.include?(?,))
- accepts
- else
- [Mime::HTML]
- end
- end
-
- # Sets the \format by string extension, which can be used to force custom formats
- # that are not controlled by the extension.
- #
- # class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone
- #
- # private
- # def adjust_format_for_iphone
- # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
- # end
- # end
- def format=(extension)
- parameters[:format] = extension.to_s
- @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
- end
-
- # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
- # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt>
- # otherwise.
- def template_format
- parameter_format = parameters[:format]
-
- if parameter_format
- parameter_format
- elsif xhr?
- :js
- else
- :html
- end
+ # Returns the content length of the request as an integer.
+ def content_length
+ super.to_i
end
# Returns true if the request's "X-Requested-With" header contains
@@ -236,7 +129,7 @@ module ActionDispatch
if @env.include? 'HTTP_CLIENT_IP'
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
# We don't know which came from the proxy, and which from the user
- raise ActionController::ActionControllerError.new(<<EOM)
+ raise ActionController::ActionControllerError.new <<EOM
IP spoofing attack?!
HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
@@ -262,124 +155,6 @@ EOM
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
- # Returns the complete URL used for this request.
- def url
- protocol + host_with_port + request_uri
- end
-
- # Returns 'https://' if this is an SSL request and 'http://' otherwise.
- def protocol
- ssl? ? 'https://' : 'http://'
- end
-
- # Is this an SSL request?
- def ssl?
- @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
- end
-
- # Returns the \host for this request, such as "example.com".
- def raw_host_with_port
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- else
- env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
- end
- end
-
- # Returns the host for this request, such as example.com.
- def host
- raw_host_with_port.sub(/:\d+$/, '')
- end
-
- # Returns a \host:\port string for this request, such as "example.com" or
- # "example.com:8080".
- def host_with_port
- "#{host}#{port_string}"
- end
-
- # Returns the port number of this request as an integer.
- def port
- if raw_host_with_port =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
- end
- end
-
- # Returns the standard \port number for this request's protocol.
- def standard_port
- case protocol
- when 'https://' then 443
- else 80
- end
- end
-
- # Returns a \port suffix like ":8080" if the \port number of this request
- # is not the default HTTP \port 80 or HTTPS \port 443.
- def port_string
- port == standard_port ? '' : ":#{port}"
- end
-
- def server_port
- @env['SERVER_PORT'].to_i
- end
-
- # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
- # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
- def domain(tld_length = 1)
- return nil unless named_host?(host)
-
- host.split('.').last(1 + tld_length).join('.')
- end
-
- # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
- # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
- # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
- # in "www.rubyonrails.co.uk".
- def subdomains(tld_length = 1)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
- end
-
- # Returns the query string, accounting for server idiosyncrasies.
- def query_string
- @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
- end
-
- # Returns the request URI, accounting for server idiosyncrasies.
- # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
- def request_uri
- if uri = @env['REQUEST_URI']
- # Remove domain, which webrick puts into the request_uri.
- (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
- else
- # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
- uri = @env['PATH_INFO'].to_s
-
- if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
- uri = uri.sub(/#{script_filename}\//, '')
- end
-
- env_qs = @env['QUERY_STRING'].to_s
- uri += "?#{env_qs}" unless env_qs.empty?
-
- if uri.blank?
- @env.delete('REQUEST_URI')
- else
- @env['REQUEST_URI'] = uri
- end
- end
- end
-
- # Returns the interpreted \path to requested resource after all the installation
- # directory of this application was taken into account.
- def path
- path = request_uri.to_s[/\A[^\?]*/]
- path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
- path
- end
-
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
@@ -390,33 +165,6 @@ EOM
@env['RAW_POST_DATA']
end
- # Returns both GET and POST \parameters in a single hash.
- def parameters
- @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
- end
- alias_method :params, :parameters
-
- def path_parameters=(parameters) #:nodoc:
- @env.delete("action_dispatch.request.symbolized_path_parameters")
- @env.delete("action_dispatch.request.parameters")
- @env["action_dispatch.request.path_parameters"] = parameters
- end
-
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
- def symbolized_path_parameters
- @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys
- end
-
- # Returns a hash with the \parameters used to form the \path of the request.
- # Returned hash keys are strings:
- #
- # {'action' => 'my_action', 'controller' => 'my_controller'}
- #
- # See <tt>symbolized_path_parameters</tt> for symbolized keys.
- def path_parameters
- @env["action_dispatch.request.path_parameters"] ||= {}
- end
-
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
@@ -432,18 +180,6 @@ EOM
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
- # Override Rack's GET method to support indifferent access
- def GET
- @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
- end
- alias_method :query_parameters, :GET
-
- # Override Rack's POST method to support indifferent access
- def POST
- @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
- end
- alias_method :request_parameters, :POST
-
def body_stream #:nodoc:
@env['rack.input']
end
@@ -461,9 +197,18 @@ EOM
@env['rack.session.options'] = options
end
- def flash
- session['flash'] || {}
+ # Override Rack's GET method to support indifferent access
+ def GET
+ @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super)
+ end
+ alias :query_parameters :GET
+
+ # Override Rack's POST method to support indifferent access
+ def POST
+ @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super)
end
+ alias :request_parameters :POST
+
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
@@ -473,77 +218,5 @@ EOM
@env['X_HTTP_AUTHORIZATION'] ||
@env['REDIRECT_X_HTTP_AUTHORIZATION']
end
-
- # Receives an array of mimes and return the first user sent mime that
- # matches the order array.
- #
- def negotiate_mime(order)
- formats.each do |priority|
- if priority == Mime::ALL
- return order.first
- elsif order.include?(priority)
- return priority
- end
- end
-
- order.include?(Mime::ALL) ? formats.first : nil
- end
-
- private
-
- def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
- end
-
- module UploadedFile
- def self.extended(object)
- object.class_eval do
- attr_accessor :original_path, :content_type
- alias_method :local_path, :path if method_defined?(:path)
- end
- end
-
- # Take the basename of the upload's original filename.
- # This handles the full Windows paths given by Internet Explorer
- # (and perhaps other broken user agents) without affecting
- # those which give the lone filename.
- # The Windows regexp is adapted from Perl's File::Basename.
- def original_filename
- unless defined? @original_filename
- @original_filename =
- unless original_path.blank?
- if original_path =~ /^(?:.*[:\\\/])?(.*)/m
- $1
- else
- File.basename original_path
- end
- end
- end
- @original_filename
- end
- end
-
- # Convert nested Hashs to HashWithIndifferentAccess and replace
- # file upload hashs with UploadedFile objects
- def normalize_parameters(value)
- case value
- when Hash
- if value.has_key?(:tempfile)
- upload = value[:tempfile]
- upload.extend(UploadedFile)
- upload.original_path = value[:filename]
- upload.content_type = value[:type]
- upload
- else
- h = {}
- value.each { |k, v| h[k] = normalize_parameters(v) }
- h.with_indifferent_access
- end
- when Array
- value.map { |e| normalize_parameters(e) }
- else
- value
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 8524bbd993..65df9b1f03 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,6 +32,8 @@ module ActionDispatch # :nodoc:
# end
# end
class Response < Rack::Response
+ include ActionDispatch::Http::Cache::Response
+
attr_accessor :request, :blank
attr_writer :header, :sending_file
@@ -55,10 +57,6 @@ module ActionDispatch # :nodoc:
yield self if block_given?
end
- def cache_control
- @cache_control ||= {}
- end
-
def status=(status)
@status = Rack::Utils.status_code(status)
end
@@ -114,33 +112,6 @@ module ActionDispatch # :nodoc:
# information.
attr_accessor :charset, :content_type
- def last_modified
- if last = headers['Last-Modified']
- Time.httpdate(last)
- end
- end
-
- def last_modified?
- headers.include?('Last-Modified')
- end
-
- def last_modified=(utc_time)
- headers['Last-Modified'] = utc_time.httpdate
- end
-
- def etag
- @etag
- end
-
- def etag?
- @etag
- end
-
- def etag=(etag)
- key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = %("#{Digest::MD5.hexdigest(key)}")
- end
-
CONTENT_TYPE = "Content-Type"
cattr_accessor(:default_charset) { "utf-8" }
@@ -222,31 +193,6 @@ module ActionDispatch # :nodoc:
end
private
- def handle_conditional_get!
- if etag? || last_modified? || !@cache_control.empty?
- set_conditional_cache_control!
- elsif nonempty_ok_response?
- self.etag = @body
-
- if request && request.etag_matches?(etag)
- self.status = 304
- self.body = []
- end
-
- set_conditional_cache_control!
- else
- headers["Cache-Control"] = "no-cache"
- end
- end
-
- def nonempty_ok_response?
- @status == 200 && string_body?
- end
-
- def string_body?
- !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
- end
-
def assign_default_content_type_and_charset!
return if headers[CONTENT_TYPE].present?
@@ -259,27 +205,5 @@ module ActionDispatch # :nodoc:
headers[CONTENT_TYPE] = type
end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
-
- def set_conditional_cache_control!
- control = @cache_control
-
- if control.empty?
- headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
- elsif @cache_control[:no_cache]
- headers["Cache-Control"] = "no-cache"
- else
- extras = control[:extras]
- max_age = control[:max_age]
-
- options = []
- options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? "public" : "private")
- options << "must-revalidate" if control[:must_revalidate]
- options.concat(extras) if extras
-
- headers["Cache-Control"] = options.join(", ")
- end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
new file mode 100644
index 0000000000..dc6121b911
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -0,0 +1,48 @@
+module ActionDispatch
+ module Http
+ module UploadedFile
+ def self.extended(object)
+ object.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path if method_defined?(:path)
+ end
+ end
+
+ # Take the basename of the upload's original filename.
+ # This handles the full Windows paths given by Internet Explorer
+ # (and perhaps other broken user agents) without affecting
+ # those which give the lone filename.
+ # The Windows regexp is adapted from Perl's File::Basename.
+ def original_filename
+ unless defined? @original_filename
+ @original_filename =
+ unless original_path.blank?
+ if original_path =~ /^(?:.*[:\\\/])?(.*)/m
+ $1
+ else
+ File.basename original_path
+ end
+ end
+ end
+ @original_filename
+ end
+ end
+
+ module Upload
+ # Convert nested Hashs to HashWithIndifferentAccess and replace
+ # file upload hashs with UploadedFile objects
+ def normalize_parameters(value)
+ if Hash === value && value.has_key?(:tempfile)
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ super
+ end
+ end
+ private :normalize_parameters
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
new file mode 100644
index 0000000000..40ceb5a9b6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -0,0 +1,129 @@
+module ActionDispatch
+ module Http
+ module URL
+ # Returns the complete URL used for this request.
+ def url
+ protocol + host_with_port + request_uri
+ end
+
+ # Returns 'https://' if this is an SSL request and 'http://' otherwise.
+ def protocol
+ ssl? ? 'https://' : 'http://'
+ end
+
+ # Is this an SSL request?
+ def ssl?
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
+ end
+
+ # Returns the \host for this request, such as "example.com".
+ def raw_host_with_port
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ else
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ # Returns the host for this request, such as example.com.
+ def host
+ raw_host_with_port.sub(/:\d+$/, '')
+ end
+
+ # Returns a \host:\port string for this request, such as "example.com" or
+ # "example.com:8080".
+ def host_with_port
+ "#{host}#{port_string}"
+ end
+
+ # Returns the port number of this request as an integer.
+ def port
+ if raw_host_with_port =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
+ end
+
+ # Returns the standard \port number for this request's protocol.
+ def standard_port
+ case protocol
+ when 'https://' then 443
+ else 80
+ end
+ end
+
+ # Returns a \port suffix like ":8080" if the \port number of this request
+ # is not the default HTTP \port 80 or HTTPS \port 443.
+ def port_string
+ port == standard_port ? '' : ":#{port}"
+ end
+
+ def server_port
+ @env['SERVER_PORT'].to_i
+ end
+
+ # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
+ # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
+ def domain(tld_length = 1)
+ return nil unless named_host?(host)
+
+ host.split('.').last(1 + tld_length).join('.')
+ end
+
+ # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
+ # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
+ # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
+ # in "www.rubyonrails.co.uk".
+ def subdomains(tld_length = 1)
+ return [] unless named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length+2)]
+ end
+
+ # Returns the query string, accounting for server idiosyncrasies.
+ def query_string
+ @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '')
+ end
+
+ # Returns the request URI, accounting for server idiosyncrasies.
+ # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
+ def request_uri
+ if uri = @env['REQUEST_URI']
+ # Remove domain, which webrick puts into the request_uri.
+ (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
+ else
+ # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
+ uri = @env['PATH_INFO'].to_s
+
+ if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
+ uri = uri.sub(/#{script_filename}\//, '')
+ end
+
+ env_qs = @env['QUERY_STRING'].to_s
+ uri += "?#{env_qs}" unless env_qs.empty?
+
+ if uri.blank?
+ @env.delete('REQUEST_URI')
+ else
+ @env['REQUEST_URI'] = uri
+ end
+ end
+ end
+
+ # Returns the interpreted \path to requested resource after all the installation
+ # directory of this application was taken into account.
+ def path
+ path = request_uri.to_s[/\A[^\?]*/]
+ path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
+ path
+ end
+
+ private
+
+ def named_host?(host)
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 49bc20f11f..5ec406e134 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,4 +1,10 @@
module ActionDispatch
+ # Provide callbacks to be executed before and after the request dispatch.
+ #
+ # It also provides a to_prepare callback, which is performed in all requests
+ # in development by only once in production and notification callback for async
+ # operations.
+ #
class Callbacks
include ActiveSupport::Callbacks
@@ -29,12 +35,6 @@ module ActionDispatch
set_callback(:call, :after, *args, &block)
end
- class << self
- # DEPRECATED
- alias_method :before_dispatch, :before
- alias_method :after_dispatch, :after
- end
-
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
run_callbacks(:prepare)
@@ -45,6 +45,8 @@ module ActionDispatch
run_callbacks(:prepare) if @prepare_each_request
@app.call(env)
end
+ ensure
+ ActiveSupport::Notifications.instrument "action_dispatch.callback"
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
new file mode 100644
index 0000000000..99b36366d6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -0,0 +1,174 @@
+module ActionDispatch
+ class Request
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
+ # to put a new one.
+ def flash
+ session['flash'] ||= Flash::FlashHash.new
+ end
+ end
+
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
+ # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
+ # then expose the flash to its template. Actually, that exposure is automatically done. Example:
+ #
+ # class PostsController < ActionController::Base
+ # def create
+ # # save post
+ # flash[:notice] = "Successfully created post"
+ # redirect_to posts_path(@post)
+ # end
+ #
+ # def show
+ # # doesn't need to assign the flash notice to the template, that's done automatically
+ # end
+ # end
+ #
+ # show.html.erb
+ # <% if flash[:notice] %>
+ # <div class="notice"><%= flash[:notice] %></div>
+ # <% end %>
+ #
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
+ # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
+ #
+ # See docs on the FlashHash class for more details about the flash.
+ class Flash
+ class FlashNow #:nodoc:
+ def initialize(flash)
+ @flash = flash
+ end
+
+ def []=(k, v)
+ @flash[k] = v
+ @flash.discard(k)
+ v
+ end
+
+ def [](k)
+ @flash[k]
+ end
+ end
+
+ class FlashHash < Hash
+ def initialize #:nodoc:
+ super
+ @used = Set.new
+ end
+
+ def []=(k, v) #:nodoc:
+ keep(k)
+ super
+ end
+
+ def update(h) #:nodoc:
+ h.keys.each { |k| keep(k) }
+ super
+ end
+
+ alias :merge! :update
+
+ def replace(h) #:nodoc:
+ @used = Set.new
+ super
+ end
+
+ # Sets a flash that will not be available to the next action, only to the current.
+ #
+ # flash.now[:message] = "Hello current action"
+ #
+ # This method enables you to use the flash as a central messaging system in your app.
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
+ # vanish when the current action is done.
+ #
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
+ def now
+ FlashNow.new(self)
+ end
+
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
+ #
+ # flash.keep # keeps the entire flash
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
+ def keep(k = nil)
+ use(k, false)
+ end
+
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
+ #
+ # flash.discard # discard the entire flash at the end of the current action
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
+ def discard(k = nil)
+ use(k)
+ end
+
+ # Mark for removal entries that were kept, and delete unkept ones.
+ #
+ # This method is called automatically by filters, so you generally don't need to care about it.
+ def sweep #:nodoc:
+ keys.each do |k|
+ unless @used.include?(k)
+ @used << k
+ else
+ delete(k)
+ @used.delete(k)
+ end
+ end
+
+ # clean up after keys that could have been left over by calling reject! or shift on the flash
+ (@used - keys).each{ |k| @used.delete(k) }
+ end
+
+ # Convenience accessor for flash[:alert]
+ def alert
+ self[:alert]
+ end
+
+ # Convenience accessor for flash[:alert]=
+ def alert=(message)
+ self[:alert] = message
+ end
+
+ # Convenience accessor for flash[:notice]
+ def notice
+ self[:notice]
+ end
+
+ # Convenience accessor for flash[:notice]=
+ def notice=(message)
+ self[:notice] = message
+ end
+
+ private
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
+ # use() # marks the entire flash as used
+ # use('msg') # marks the "msg" entry as used
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
+ # if no key is passed.
+ def use(key = nil, used = true)
+ Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
+ return key ? self[key] : self
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if (session = env['rack.session']) && (flash = session['flash'])
+ flash.sweep
+ end
+
+ @app.call(env)
+ ensure
+ if (session = env['rack.session']) && (flash = session['flash']) && flash.empty?
+ session.delete('flash')
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb
new file mode 100644
index 0000000000..56e2d2f2a8
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/head.rb
@@ -0,0 +1,18 @@
+module ActionDispatch
+ class Head
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if env["REQUEST_METHOD"] == "HEAD"
+ env["REQUEST_METHOD"] = "GET"
+ env["rack.methodoverride.original_method"] = "HEAD"
+ status, headers, body = @app.call(env)
+ [status, headers, []]
+ else
+ @app.call(env)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 7d4f0998ce..311880cabc 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -102,7 +102,7 @@ module ActionDispatch
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
- raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
end
retry
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 4ebc8a2ab9..10f04dcdf6 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,7 +1,24 @@
require 'active_support/core_ext/exception'
+require 'active_support/notifications'
require 'action_dispatch/http/request'
module ActionDispatch
+ # This middleware rescues any exception returned by the application and renders
+ # nice exception pages if it's being rescued locally.
+ #
+ # Every time an exception is caught, a notification is published, becoming a good API
+ # to deal with exceptions. So, if you want send an e-mail through ActionMailer
+ # everytime this notification is published, you just need to do the following:
+ #
+ # ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload|
+ # ExceptionNotifier.deliver_exception(start, payload)
+ # end
+ #
+ # The payload is a hash which has two pairs:
+ #
+ # * :env - Contains the rack env for the given request;
+ # * :exception - The exception raised;
+ #
class ShowExceptions
LOCALHOST = '127.0.0.1'.freeze
@@ -44,8 +61,11 @@ module ActionDispatch
def call(env)
@app.call(env)
rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
+ ActiveSupport::Notifications.instrument 'action_dispatch.show_exception',
+ :env => env, :exception => exception do
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ render_exception(env, exception)
+ end
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/string_coercion.rb b/actionpack/lib/action_dispatch/middleware/string_coercion.rb
deleted file mode 100644
index 232e947835..0000000000
--- a/actionpack/lib/action_dispatch/middleware/string_coercion.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module ActionDispatch
- class StringCoercion
- class UglyBody < ActiveSupport::BasicObject
- def initialize(body)
- @body = body
- end
-
- def each
- @body.each do |part|
- yield part.to_s
- end
- end
-
- private
- def method_missing(*args, &block)
- @body.__send__(*args, &block)
- end
- end
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- [status, headers, UglyBody.new(body)]
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 8f33346a4f..9aaa4355f2 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -68,16 +68,11 @@ module ActionDispatch
end
def normalize_path(path)
- path = nil if path == ""
- path = "#{@scope[:path]}#{path}" if @scope[:path]
- path = Rack::Mount::Utils.normalize_path(path) if path
-
- raise ArgumentError, "path is required" unless path
-
- path
+ path = "#{@scope[:path]}/#{path}"
+ raise ArgumentError, "path is required" if path.empty?
+ Mapper.normalize_path(path)
end
-
def app
Constraints.new(
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
@@ -123,7 +118,6 @@ module ActionDispatch
end
end
-
def blocks
if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
block = @options[:constraints]
@@ -162,6 +156,14 @@ module ActionDispatch
end
end
+ # Invokes Rack::Mount::Utils.normalize path and ensure that
+ # (:locale) becomes (/:locale) instead of /(:locale).
+ def self.normalize_path(path)
+ path = Rack::Mount::Utils.normalize_path(path)
+ path.sub!(%r{/\(+/?:}, '(/:')
+ path
+ end
+
module Base
def initialize(set)
@set = set
@@ -200,13 +202,22 @@ module ActionDispatch
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc { |params| path % params }
status = options[:status] || 301
+ body = 'Moved Permanently'
lambda do |env|
- req = Rack::Request.new(env)
- params = path_proc.call(env["action_dispatch.request.path_parameters"])
- url = req.scheme + '://' + req.host + params
+ req = Request.new(env)
+
+ uri = URI.parse(path_proc.call(req.params.symbolize_keys))
+ uri.scheme ||= req.scheme
+ uri.host ||= req.host
+ uri.port ||= req.port unless req.port == 80
- [ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ]
+ headers = {
+ 'Location' => uri.to_s,
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+ [ status, headers, [body] ]
end
end
@@ -236,46 +247,35 @@ module ActionDispatch
options[:controller] = args.first
end
- if path = options.delete(:path)
- path_set = true
- path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s)
- else
- path_set = false
- end
+ recover = {}
- if name_prefix = options.delete(:name_prefix)
- name_prefix_set = true
- name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix)
- else
- name_prefix_set = false
+ options[:constraints] ||= {}
+ unless options[:constraints].is_a?(Hash)
+ block, options[:constraints] = options[:constraints], {}
end
- if controller = options.delete(:controller)
- controller_set = true
- controller, @scope[:controller] = @scope[:controller], controller
- else
- controller_set = false
+ scope_options.each do |option|
+ if value = options.delete(option)
+ recover[option] = @scope[option]
+ @scope[option] = send("merge_#{option}_scope", @scope[option], value)
+ end
end
- constraints = options.delete(:constraints) || {}
- unless constraints.is_a?(Hash)
- block, constraints = constraints, {}
- end
- constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints)
- blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block]
+ recover[:block] = @scope[:blocks]
+ @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
- options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
+ recover[:options] = @scope[:options]
+ @scope[:options] = merge_options_scope(@scope[:options], options)
yield
-
self
ensure
- @scope[:path] = path if path_set
- @scope[:name_prefix] = name_prefix if name_prefix_set
- @scope[:controller] = controller if controller_set
- @scope[:options] = options
- @scope[:blocks] = blocks
- @scope[:constraints] = constraints
+ scope_options.each do |option|
+ @scope[option] = recover[option] if recover.has_key?(option)
+ end
+
+ @scope[:options] = recover[:options]
+ @scope[:blocks] = recover[:block]
end
def controller(controller)
@@ -283,7 +283,7 @@ module ActionDispatch
end
def namespace(path)
- scope("/#{path}") { yield }
+ scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield }
end
def constraints(constraints = {})
@@ -304,25 +304,83 @@ module ActionDispatch
args.push(options)
super(*args)
end
+
+ private
+ def scope_options
+ @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
+ end
+
+ def merge_path_scope(parent, child)
+ Mapper.normalize_path("#{parent}/#{child}")
+ end
+
+ def merge_name_prefix_scope(parent, child)
+ parent ? "#{parent}_#{child}" : child
+ end
+
+ def merge_namespace_scope(parent, child)
+ parent ? "#{parent}/#{child}" : child
+ end
+
+ def merge_controller_scope(parent, child)
+ @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child
+ end
+
+ def merge_resources_path_names_scope(parent, child)
+ merge_options_scope(parent, child)
+ end
+
+ def merge_constraints_scope(parent, child)
+ merge_options_scope(parent, child)
+ end
+
+ def merge_blocks_scope(parent, child)
+ (parent || []) + [child]
+ end
+
+ def merge_options_scope(parent, child)
+ (parent || {}).merge(child)
+ end
end
module Resources
+ CRUD_ACTIONS = [:index, :show, :create, :update, :destroy]
+
class Resource #:nodoc:
- attr_reader :plural, :singular
+ def self.default_actions
+ [:index, :create, :new, :show, :update, :destroy, :edit]
+ end
+
+ attr_reader :plural, :singular, :options
def initialize(entities, options = {})
entities = entities.to_s
+ @options = options
@plural = entities.pluralize
@singular = entities.singularize
end
+ def default_actions
+ self.class.default_actions
+ end
+
+ def actions
+ if only = options[:only]
+ only.map(&:to_sym)
+ elsif except = options[:except]
+ default_actions - except.map(&:to_sym)
+ else
+ default_actions
+ end
+ end
+
def name
- plural
+ options[:as] || plural
end
def controller
- plural
+ options[:controller] || plural
end
def member_name
@@ -339,15 +397,24 @@ module ActionDispatch
end
class SingletonResource < Resource #:nodoc:
+ def self.default_actions
+ [:show, :create, :update, :destroy, :new, :edit]
+ end
+
def initialize(entity, options = {})
super
end
def name
- singular
+ options[:as] || singular
end
end
+ def initialize(*args)
+ super
+ @scope[:resources_path_names] = @set.resources_path_names
+ end
+
def resource(*resources, &block)
options = resources.extract_options!
@@ -357,7 +424,14 @@ module ActionDispatch
return self
end
- resource = SingletonResource.new(resources.pop)
+ if path_names = options.delete(:path_names)
+ scope(:resources_path_names => path_names) do
+ resource(resources, options)
+ end
+ return self
+ end
+
+ resource = SingletonResource.new(resources.pop, options)
if @scope[:scope_level] == :resources
nested do
@@ -366,16 +440,16 @@ module ActionDispatch
return self
end
- scope(:path => "/#{resource.name}", :controller => resource.controller) do
+ scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resource, resource) do
yield if block_given?
- get "(.:format)", :to => :show, :as => resource.member_name
- post "(.:format)", :to => :create
- put "(.:format)", :to => :update
- delete "(.:format)", :to => :destroy
- get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}"
- get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}"
+ get :show, :as => resource.member_name if resource.actions.include?(:show)
+ post :create if resource.actions.include?(:create)
+ put :update if resource.actions.include?(:update)
+ delete :destroy if resource.actions.include?(:destroy)
+ get :new, :as => resource.singular if resource.actions.include?(:new)
+ get :edit, :as => resource.singular if resource.actions.include?(:edit)
end
end
@@ -391,7 +465,14 @@ module ActionDispatch
return self
end
- resource = Resource.new(resources.pop)
+ if path_names = options.delete(:path_names)
+ scope(:resources_path_names => path_names) do
+ resources(resources, options)
+ end
+ return self
+ end
+
+ resource = Resource.new(resources.pop, options)
if @scope[:scope_level] == :resources
nested do
@@ -400,28 +481,22 @@ module ActionDispatch
return self
end
- scope(:path => "/#{resource.name}", :controller => resource.controller) do
+ scope(:path => resource.name.to_s, :controller => resource.controller) do
with_scope_level(:resources, resource) do
yield if block_given?
with_scope_level(:collection) do
- get "(.:format)", :to => :index, :as => resource.collection_name
- post "(.:format)", :to => :create
-
- with_exclusive_name_prefix :new do
- get "/new(.:format)", :to => :new, :as => resource.singular
- end
+ get :index, :as => resource.collection_name if resource.actions.include?(:index)
+ post :create if resource.actions.include?(:create)
+ get :new, :as => resource.singular if resource.actions.include?(:new)
end
with_scope_level(:member) do
- scope("/:id") do
- get "(.:format)", :to => :show, :as => resource.member_name
- put "(.:format)", :to => :update
- delete "(.:format)", :to => :destroy
-
- with_exclusive_name_prefix :edit do
- get "/edit(.:format)", :to => :edit, :as => resource.singular
- end
+ scope(':id') do
+ get :show, :as => resource.member_name if resource.actions.include?(:show)
+ put :update if resource.actions.include?(:update)
+ delete :destroy if resource.actions.include?(:destroy)
+ get :edit, :as => resource.singular if resource.actions.include?(:edit)
end
end
end
@@ -448,7 +523,7 @@ module ActionDispatch
end
with_scope_level(:member) do
- scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do
+ scope(':id', :name_prefix => parent_resource.member_name, :as => "") do
yield
end
end
@@ -460,7 +535,7 @@ module ActionDispatch
end
with_scope_level(:nested) do
- scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do
+ scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do
yield
end
end
@@ -474,9 +549,22 @@ module ActionDispatch
return self
end
+ resources_path_names = options.delete(:path_names)
+
if args.first.is_a?(Symbol)
- with_exclusive_name_prefix(args.first) do
- return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym))
+ action = args.first
+ if CRUD_ACTIONS.include?(action)
+ begin
+ old_path = @scope[:path]
+ @scope[:path] = "#{@scope[:path]}(.:format)"
+ return match(options.reverse_merge(:to => action))
+ ensure
+ @scope[:path] = old_path
+ end
+ else
+ with_exclusive_name_prefix(action) do
+ return match("#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action))
+ end
end
end
@@ -502,6 +590,11 @@ module ActionDispatch
end
private
+ def action_path(name, path_names = nil)
+ path_names ||= @scope[:resources_path_names]
+ path_names[name.to_sym] || name.to_s
+ end
+
def with_exclusive_name_prefix(prefix)
begin
old_name_prefix = @scope[:name_prefix]
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index bd397432ce..660d28dbec 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -74,9 +74,8 @@ module ActionDispatch
@routes = {}
@helpers = []
- @module ||= Module.new
- @module.instance_methods.each do |selector|
- @module.class_eval { remove_method selector }
+ @module ||= Module.new do
+ instance_methods.each { |selector| remove_method(selector) }
end
end
@@ -138,67 +137,87 @@ module ActionDispatch
end
end
- def named_helper_module_eval(code, *args)
- @module.module_eval(code, *args)
- end
-
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
+
+ # We use module_eval to avoid leaks
+ @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(options = nil) # def hash_for_users_url(options = nil)
options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
end # end
protected :#{selector} # protected :hash_for_users_url
- end_eval
+ END_EVAL
helpers << selector
end
+ # Create a url helper allowing ordered parameters to be associated
+ # with corresponding dynamic segments, so you can do:
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # Instead of:
+ #
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
+ #
+ # Also allow options hash, so you can do:
+ #
+ # foo_url(bar, baz, bang, :sort_by => 'baz')
+ #
def define_url_helper(route, name, kind, options)
selector = url_helper_name(name, kind)
- # The segment keys used for positional parameters
-
hash_access_method = hash_access_name(name, kind)
- # allow ordered parameters to be associated with corresponding
- # dynamic segments, so you can do
+ # We use module_eval to avoid leaks.
#
- # foo_url(bar, baz, bang)
+ # def users_url(*args)
+ # if args.empty? || Hash === args.first
+ # options = hash_for_users_url(args.first || {})
+ # else
+ # options = hash_for_users_url(args.extract_options!)
+ # default = default_url_options(options) if self.respond_to?(:default_url_options, true)
+ # options = (default ||= {}).merge(options)
#
- # instead of
+ # keys = []
+ # keys -= options.keys if args.size < keys.size - 1
#
- # foo_url(:bar => bar, :baz => baz, :bang => bang)
+ # args = args.zip(keys).inject({}) do |h, (v, k)|
+ # h[k] = v
+ # h
+ # end
#
- # Also allow options hash, so you can do
+ # # Tell url_for to skip default_url_options
+ # options[:use_defaults] = false
+ # options.merge!(args)
+ # end
#
- # foo_url(bar, baz, bang, :sort_by => 'baz')
- #
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args) # def users_url(*args)
- #
- opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
- args.first || {} # args.first || {}
- else # else
- options = args.extract_options! # options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
- h[k] = v # h[k] = v
- h # h
- end # end
- options.merge(args) # options.merge(args)
- end # end
- #
- url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
- #
- end # end
- #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args) # def formatted_users_url(*args)
- ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
- "Please pass format to the standard " + # "Please pass format to the standard " +
- "#{selector} method instead.", caller) # "users_url method instead.", caller)
- #{selector}(*args) # users_url(*args)
- end # end
- protected :#{selector} # protected :users_url
- end_eval
+ # url_for(options)
+ # end
+ @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
+ def #{selector}(*args)
+ if args.empty? || Hash === args.first
+ options = #{hash_access_method}(args.first || {})
+ else
+ options = #{hash_access_method}(args.extract_options!)
+ default = default_url_options(options) if self.respond_to?(:default_url_options, true)
+ options = (default ||= {}).merge(options)
+
+ keys = #{route.segment_keys.inspect}
+ keys -= options.keys if args.size < keys.size - 1 # take format into account
+
+ args = args.zip(keys).inject({}) do |h, (v, k)|
+ h[k] = v
+ h
+ end
+
+ # Tell url_for to skip default_url_options
+ options[:use_defaults] = false
+ options.merge!(args)
+ end
+
+ url_for(options)
+ end
+ protected :#{selector}
+ END_EVAL
helpers << selector
end
end
@@ -206,9 +225,16 @@ module ActionDispatch
attr_accessor :routes, :named_routes
attr_accessor :disable_clear_and_finalize
+ def self.default_resources_path_names
+ { :new => 'new', :edit => 'edit' }
+ end
+
+ attr_accessor :resources_path_names
+
def initialize
self.routes = []
self.named_routes = NamedRouteCollection.new
+ self.resources_path_names = self.class.default_resources_path_names
@disable_clear_and_finalize = false
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 5686bbdbde..c2486d3730 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -2,6 +2,15 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from Rails applications.
module ResponseAssertions
+ extend ActiveSupport::Concern
+
+ included do
+ # TODO: Need to pull in AV::Template monkey patches that track which
+ # templates are rendered. assert_template should probably be part
+ # of AV instead of AD.
+ require 'action_view/test_case'
+ end
+
# Asserts that the response is one of the following types:
#
# * <tt>:success</tt> - Status code was 200
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index c2dc591ff7..a6b1126e2b 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -524,7 +524,7 @@ module ActionDispatch
fix_content = lambda do |node|
# Gets around a bug in the Rails 1.1 HTML parser.
- node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { CGI.escapeHTML($1) }
+ node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) }
end
selected = elements.map do |element|
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 2a5f5dcd5c..4ec47d146c 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,6 +1,5 @@
require 'stringio'
require 'uri'
-require 'active_support/test_case'
require 'active_support/core_ext/object/metaclass'
require 'rack/test'
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 8ce6e82524..93aa69c060 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -52,6 +52,8 @@ module ActionView
autoload :TemplateHandler, 'action_view/template'
autoload :TemplateHandlers, 'action_view/template'
end
+
+ autoload :TestCase, 'action_view/test_case'
end
require 'action_view/erb/util'
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index c70f29f098..87b7adf6c4 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -216,7 +216,7 @@ module ActionView
end
options[:object_name] ||= params.first
- I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale|
+ I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 4b51dc7856..34f38b0a8a 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -616,7 +616,7 @@ module ActionView
build_selects_from_types(order)
else
- "#{select_date}#{@options[:datetime_separator]}#{select_time}"
+ "#{select_date}#{@options[:datetime_separator]}#{select_time}".html_safe!
end
end
@@ -835,7 +835,7 @@ module ActionView
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html.to_s
- content_tag(:select, select_html, select_options) + "\n"
+ (content_tag(:select, select_html, select_options) + "\n").html_safe!
end
# Builds a prompt option tag with supplied options or from default options
@@ -860,12 +860,12 @@ module ActionView
# build_hidden(:year, 2008)
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
def build_hidden(type, value)
- tag(:input, {
+ (tag(:input, {
:type => "hidden",
:id => input_id_from_type(type),
:name => input_name_from_type(type),
:value => value
- }) + "\n"
+ }) + "\n").html_safe!
end
# Returns the name attribute for the input tag
@@ -896,7 +896,7 @@ module ActionView
separator = separator(type) unless type == order.first # don't add on last field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
- select
+ select.html_safe!
end
# Returns the separator for a given datetime component
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 81c9c88820..20e9916d62 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -505,7 +505,7 @@ module ActionView
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
- # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly.
+ # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
# target labels for radio_button tags (where the value is used in the ID of the input tag).
@@ -517,8 +517,8 @@ module ActionView
# You can localize your labels based on model and attribute names.
# For example you can define the following in your locale (e.g. en.yml)
#
- # views:
- # labels:
+ # helpers:
+ # label:
# post:
# body: "Write your entire text here"
#
@@ -777,7 +777,7 @@ module ActionView
options["for"] ||= name_and_id["id"]
content = if text.blank?
- I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence
+ I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
else
text.to_s
end
@@ -798,7 +798,7 @@ module ActionView
if field_type == "hidden"
options.delete("size")
end
- options["type"] = field_type
+ options["type"] ||= field_type
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
options["value"] &&= html_escape(options["value"])
add_default_name_and_id(options)
@@ -842,7 +842,12 @@ module ActionView
checked = self.class.check_box_checked?(value(object), checked_value)
end
options["checked"] = "checked" if checked
- add_default_name_and_id(options)
+ if options["multiple"]
+ add_default_name_and_id_for_value(checked_value, options)
+ options.delete("multiple")
+ else
+ add_default_name_and_id(options)
+ end
hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
checkbox = tag("input", options)
(hidden + checkbox).html_safe!
@@ -1058,7 +1063,7 @@ module ActionView
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
-
+
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@template.hidden_field(@object_name, method, objectify_options(options))
@@ -1072,7 +1077,36 @@ module ActionView
@template.error_messages_for(@object_name, objectify_options(options))
end
- def submit(value = "Save changes", options = {})
+ # Add the submit button for the given form. When no value is given, it checks
+ # if the object is a new resource or not to create the proper label:
+ #
+ # <% form_for @post do |f| %>
+ # <%= f.submit %>
+ # <% end %>
+ #
+ # In the example above, if @post is a new record, it will use "Create Post" as
+ # submit button label, otherwise, it uses "Update Post".
+ #
+ # Those labels can be customized using I18n, under the helpers.submit key and accept
+ # the {{model}} as translation interpolation:
+ #
+ # en:
+ # helpers:
+ # submit:
+ # create: "Create a {{model}}"
+ # update: "Confirm changes to {{model}}"
+ #
+ # It also searches for a key specific for the given object:
+ #
+ # en:
+ # helpers:
+ # submit:
+ # post:
+ # create: "Add {{model}}"
+ #
+ def submit(value=nil, options={})
+ value, options = nil, value if value.is_a?(Hash)
+ value ||= submit_default_value
@template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
end
@@ -1085,6 +1119,24 @@ module ActionView
@default_options.merge(options.merge(:object => @object))
end
+ def submit_default_value
+ object = @object.respond_to?(:to_model) ? @object.to_model : @object
+ key = object ? (object.new_record? ? :create : :update) : :submit
+
+ model = if object.class.respond_to?(:model_name)
+ object.class.model_name.human
+ else
+ @object_name.to_s.humanize
+ end
+
+ defaults = []
+ defaults << :"helpers.submit.#{object_name}.#{key}"
+ defaults << :"helpers.submit.#{key}"
+ defaults << "#{key.to_s.humanize} #{model}"
+
+ I18n.t(defaults.shift, :model => model, :default => defaults)
+ end
+
def nested_attributes_association?(association_name)
@object.respond_to?("#{association_name}_attributes=")
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 935ab5f3e8..02ad637509 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -571,7 +571,7 @@ module ActionView
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
end
if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select')
+ prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
"<option value=\"\">#{prompt}</option>\n" + option_tags
else
option_tags
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 397871b85e..64b71663c3 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -92,7 +92,7 @@ module ActionView
:precision => precision,
:delimiter => delimiter,
:separator => separator)
- ).gsub(/%u/, unit)
+ ).gsub(/%u/, unit).html_safe!
rescue
number
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 8c1f0ad81f..ff7bc3b34e 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -1030,7 +1030,7 @@ module ActionView
# page.hide 'spinner'
# end
def update_page(&block)
- JavaScriptGenerator.new(@template, &block).to_s
+ JavaScriptGenerator.new(@template, &block).to_s.html_safe!
end
# Works like update_page but wraps the generated JavaScript in a <script>
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index a3bee3e8c2..814d86812d 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -565,7 +565,7 @@ module ActionView
end
link_text = block_given?? yield(href) : href
- href = 'http://' + href unless href.index('http') == 0
+ href = 'http://' + href unless href =~ %r{^[a-z]+://}i
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 5b136d4f54..14628c5404 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -11,6 +11,11 @@ module ActionView
module UrlHelper
include JavaScriptHelper
+ # Need to map default url options to controller one.
+ def default_url_options(*args) #:nodoc:
+ @controller.send(:default_url_options, *args)
+ end
+
# Returns the URL for the set of +options+ provided. This takes the
# same options as +url_for+ in Action Controller (see the
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
@@ -461,10 +466,10 @@ module ActionView
string = ''
extras = ''
- extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
- extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
- extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
- extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
+ extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}&" unless cc.nil?
+ extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
+ extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}&" unless body.nil?
+ extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil?
extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
email_address = email_address.to_s
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 5e2a92b89a..a3548051c1 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -9,7 +9,7 @@
delimiter: ","
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 3
-
+
# Used in number_to_currency()
currency:
format:
@@ -20,15 +20,15 @@
separator: "."
delimiter: ","
precision: 2
-
+
# Used in number_to_percentage()
percentage:
format:
# These three are to override number.format and are optional
- # separator:
+ # separator:
delimiter: ""
- # precision:
-
+ # precision:
+
# Used in number_to_precision()
precision:
format:
@@ -36,12 +36,12 @@
# separator:
delimiter: ""
# precision:
-
+
# Used in number_to_human_size()
human:
format:
# These three are to override number.format and are optional
- # separator:
+ # separator:
delimiter: ""
precision: 1
storage_units:
@@ -102,16 +102,22 @@
minute: "Minute"
second: "Seconds"
- activemodel:
- errors:
- template:
- header:
- one: "1 error prohibited this {{model}} from being saved"
- other: "{{count}} errors prohibited this {{model}} from being saved"
- # The variable :count is also available
- body: "There were problems with the following fields:"
+ errors:
+ template:
+ header:
+ one: "1 error prohibited this {{model}} from being saved"
+ other: "{{count}} errors prohibited this {{model}} from being saved"
+ # The variable :count is also available
+ body: "There were problems with the following fields:"
- support:
+ helpers:
select:
- # default value for :prompt => true in FormOptionsHelper
+ # Default value for :prompt => true in FormOptionsHelper
prompt: "Please select"
+
+ # Default translation keys for submit FormHelper
+ submit:
+ create: 'Create {{model}}'
+ update: 'Update {{model}}'
+ submit: 'Save {{model}}'
+
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index a90e0636b9..968dc7b25e 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -1,2 +1,17 @@
require "action_view"
-require "rails" \ No newline at end of file
+require "rails"
+
+module ActionView
+ class Railtie < Rails::Railtie
+ plugin_name :action_view
+
+ require "action_view/railties/subscriber"
+ subscriber ActionView::Railties::Subscriber.new
+
+ initializer "action_view.cache_asset_timestamps" do |app|
+ unless app.config.cache_classes
+ ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/railties/subscriber.rb b/actionpack/lib/action_view/railties/subscriber.rb
new file mode 100644
index 0000000000..803f19379c
--- /dev/null
+++ b/actionpack/lib/action_view/railties/subscriber.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def render_template(event)
+ message = "Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << (" (%.1fms)" % event.duration)
+ info(message)
+ end
+ alias :render_partial :render_template
+ alias :render_collection :render_template
+
+ def logger
+ ActionController::Base.logger
+ end
+
+ protected
+
+ def from_rails_root(string)
+ string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "")
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index 5158415c20..8c936ae09e 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -212,34 +212,34 @@ module ActionView
end
def render
- options = @options
+ identifier = ((@template = find_template) ? @template.identifier : @path)
if @collection
- ActiveSupport::Notifications.instrument(:render_collection, :path => @path,
- :count => @collection.size) do
+ ActiveSupport::Notifications.instrument("action_view.render_collection",
+ :identifier => identifier || "collection", :count => @collection.size) do
render_collection
end
else
- content = ActiveSupport::Notifications.instrument(:render_partial, :path => @path) do
+ content = ActiveSupport::Notifications.instrument("action_view.render_partial",
+ :identifier => identifier) do
render_partial
end
- if !@block && options[:layout]
- content = @view._render_layout(find_template(options[:layout]), @locals){ content }
+ if !@block && (layout = @options[:layout])
+ content = @view._render_layout(find_template(layout), @locals){ content }
end
content
end
end
def render_collection
- @template = template = find_template
return nil if @collection.blank?
if @options.key?(:spacer_template)
spacer = find_template(@options[:spacer_template]).render(@view, @locals)
end
- result = template ? collection_with_template : collection_without_template
+ result = @template ? collection_with_template : collection_without_template
result.join(spacer).html_safe!
end
@@ -277,7 +277,6 @@ module ActionView
end
def render_partial(object = @object)
- @template = template = find_template
locals, view = @locals, @view
object ||= locals[template.variable_name]
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index 48316cac53..ec278ca783 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -93,25 +93,23 @@ module ActionView
def _render_template(template, layout = nil, options = {})
locals = options[:locals] || {}
- content = ActiveSupport::Notifications.instrument(:render_template,
- :identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do
- template.render(self, locals)
- end
+ ActiveSupport::Notifications.instrument("action_view.render_template",
+ :identifier => template.identifier, :layout => layout.try(:identifier)) do
- @_content_for[:layout] = content
+ content = template.render(self, locals)
+ @_content_for[:layout] = content
- if layout
- @_layout = layout.identifier
- content = _render_layout(layout, locals)
- end
+ if layout
+ @_layout = layout.identifier
+ content = _render_layout(layout, locals)
+ end
- content
+ content
+ end
end
def _render_layout(layout, locals, &block)
- ActiveSupport::Notifications.instrument(:render_layout, :identifier => layout.identifier) do
- layout.render(self, locals){ |*name| _layout_for(*name, &block) }
- end
+ layout.render(self, locals){ |*name| _layout_for(*name, &block) }
end
end
end
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index 67e086d8bd..2abb352d4e 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -13,7 +13,7 @@ module ActionView #:nodoc:
end
def identifier
- self
+ 'text template'
end
def inspect
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index be9a2ed50d..16d66b6eca 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -1,6 +1,3 @@
-require 'active_support/test_case'
-require 'action_controller/test_case'
-
module ActionView
class Base
alias_method :initialize_without_template_tracking, :initialize
diff --git a/actionpack/test/abstract/url_for_test.rb b/actionpack/test/abstract/url_for_test.rb
new file mode 100644
index 0000000000..e5570349b8
--- /dev/null
+++ b/actionpack/test/abstract/url_for_test.rb
@@ -0,0 +1,272 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+
+ class UrlForTests < ActionController::TestCase
+ class W
+ include AbstractController::UrlFor
+ end
+
+ def teardown
+ W.default_url_options.clear
+ end
+
+ def add_host!
+ W.default_url_options[:host] = 'www.basecamphq.com'
+ end
+
+ def test_exception_is_thrown_without_host
+ assert_raise RuntimeError do
+ W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
+ end
+ end
+
+ def test_anchor
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
+ )
+ end
+
+ def test_anchor_should_call_to_param
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
+ )
+ end
+
+ def test_anchor_should_be_cgi_escaped
+ assert_equal('/c/a#anc%2Fhor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
+ )
+ end
+
+ def test_default_host
+ add_host!
+ assert_equal('http://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_host_may_be_overridden
+ add_host!
+ assert_equal('http://37signals.basecamphq.com/c/a/i',
+ W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_port
+ add_host!
+ assert_equal('http://www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
+ )
+ end
+
+ def test_protocol
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ end
+
+ def test_protocol_with_and_without_separator
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
+ )
+ end
+
+ def test_trailing_slash
+ add_host!
+ options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
+ assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
+ end
+
+ def test_trailing_slash_with_protocol
+ add_host!
+ options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'}
+ assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
+ assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'}))
+ end
+
+ def test_trailing_slash_with_only_path
+ options = {:controller => 'foo', :trailing_slash => true}
+ assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true}))
+ options.update({:action => 'bar', :id => '33'})
+ assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true}))
+ assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true}))
+ end
+
+ def test_trailing_slash_with_anchor
+ options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'}
+ assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options)
+ assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'}))
+ end
+
+ def test_trailing_slash_with_params
+ url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'cafe' }.to_query
+ assert_equal params[1], { :p2 => 'link' }.to_query
+ end
+
+ def test_relative_url_root_is_respected
+ orig_relative_url_root = ActionController::Base.relative_url_root
+ ActionController::Base.relative_url_root = '/subdir'
+
+ add_host!
+ assert_equal('https://www.basecamphq.com/subdir/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ ensure
+ ActionController::Base.relative_url_root = orig_relative_url_root
+ end
+
+ def test_named_routes
+ with_routing do |set|
+ set.draw do |map|
+ match 'this/is/verbose', :to => 'home#index', :as => :no_args
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include AbstractController::UrlFor }
+ controller = kls.new
+ assert controller.respond_to?(:home_url)
+ assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
+ assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
+ assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
+ end
+ end
+
+ def test_relative_url_root_is_respected_for_named_routes
+ orig_relative_url_root = ActionController::Base.relative_url_root
+ ActionController::Base.relative_url_root = '/subdir'
+
+ with_routing do |set|
+ set.draw do |map|
+ match '/home/sweet/home/:user', :to => 'home#index', :as => :home
+ end
+
+ kls = Class.new { include AbstractController::UrlFor }
+ controller = kls.new
+
+ assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+ end
+ ensure
+ ActionController::Base.relative_url_root = orig_relative_url_root
+ end
+
+ def test_only_path
+ with_routing do |set|
+ set.draw do |map|
+ match 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ match ':controller/:action/:id'
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include AbstractController::UrlFor }
+ controller = kls.new
+ assert controller.respond_to?(:home_url)
+ assert_equal '/brave/new/world',
+ controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
+
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
+ end
+ end
+
+ def test_one_parameter
+ assert_equal('/c/a?param=val',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
+ )
+ end
+
+ def test_two_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'X1' }.to_query
+ assert_equal params[1], { :p2 => 'Y2' }.to_query
+ end
+
+ def test_hash_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[category]' => 'prof' }.to_query
+ assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
+ end
+
+ def test_array_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
+ params = extract_params(url)
+ assert_equal params[0], { 'query[]' => 'Bob' }.to_query
+ assert_equal params[1], { 'query[]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_and_array_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
+ assert_match %r(^/c/a/101), url
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
+ end
+
+ def test_path_generation_for_symbol_parameter_keys
+ assert_generates("/image", :controller=> :image)
+ end
+
+ def test_named_routes_with_nil_keys
+ with_routing do |set|
+ set.draw do |map|
+ match 'posts.:format', :to => 'posts#index', :as => :posts
+ match '/', :to => 'posts#index', :as => :main
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include AbstractController::UrlFor }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
+ controller = kls.new
+ params = {:action => :index, :controller => :posts, :format => :xml}
+ assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
+ params[:format] = nil
+ assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
+ end
+ end
+
+ def test_multiple_includes_maintain_distinct_options
+ first_class = Class.new { include AbstractController::UrlFor }
+ second_class = Class.new { include AbstractController::UrlFor }
+
+ first_host, second_host = 'firsthost.com', 'secondhost.com'
+
+ first_class.default_url_options[:host] = first_host
+ second_class.default_url_options[:host] = second_host
+
+ assert_equal first_class.default_url_options[:host], first_host
+ assert_equal second_class.default_url_options[:host], second_host
+ end
+
+ private
+ def extract_params(url)
+ url.split('?', 2).last.split('&').sort
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 8c65087898..10913c0fdb 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -19,8 +19,6 @@ require 'action_view'
require 'action_view/base'
require 'action_dispatch'
require 'fixture_template'
-require 'active_support/test_case'
-require 'action_view/test_case'
require 'active_support/dependencies'
activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
@@ -50,14 +48,6 @@ ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
-# Turn on notifications
-require 'active_support/notifications'
-Thread.abort_on_exception = true
-
-ActiveSupport::Notifications.subscribe do |*args|
- ActionController::Base.log_event(*args) if ActionController::Base.logger
-end
-
module SetupOnce
extend ActiveSupport::Concern
@@ -95,29 +85,15 @@ class ActiveSupport::TestCase
end
end
-class MockLogger
- attr_reader :logged
- attr_accessor :level
-
- def initialize
- @level = Logger::DEBUG
- @logged = []
- end
-
- def method_missing(method, *args, &blk)
- @logged << args.first
- @logged << blk.call if block_given?
- end
-end
-
class ActionController::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
+ ActionDispatch::Flash
ActionDispatch::MiddlewareStack.new { |middleware|
- middleware.use "ActionDispatch::StringCoercion"
middleware.use "ActionDispatch::ShowExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
- middleware.use "Rack::Head"
+ middleware.use "ActionDispatch::Flash"
+ middleware.use "ActionDispatch::Head"
}.build(routes || ActionController::Routing::Routes)
end
diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb
index 9a094cf66b..4f2b052720 100644
--- a/actionpack/test/active_record_unit.rb
+++ b/actionpack/test/active_record_unit.rb
@@ -17,7 +17,6 @@ unless defined?(ActiveRecord) && defined?(Fixtures)
raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR)
$LOAD_PATH.unshift PATH_TO_AR
require 'active_record'
- require 'active_record/fixtures'
rescue LoadError => e
$stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}"
ActiveRecordTestConnector.able_to_connect = false
diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb
index 0f534da14b..37c7738301 100644
--- a/actionpack/test/activerecord/controller_runtime_test.rb
+++ b/actionpack/test/activerecord/controller_runtime_test.rb
@@ -1,39 +1,53 @@
require 'active_record_unit'
require 'active_record/railties/controller_runtime'
require 'fixtures/project'
+require 'rails/subscriber/test_helper'
+require 'action_controller/railties/subscriber'
ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
-class ARLoggingController < ActionController::Base
- def show
- render :inline => "<%= Project.all %>"
+module ControllerRuntimeSubscriberTest
+ class SubscriberController < ActionController::Base
+ def show
+ render :inline => "<%= Project.all %>"
+ end
end
-end
-class ARLoggingTest < ActionController::TestCase
- tests ARLoggingController
+ def self.included(base)
+ base.tests SubscriberController
+ end
def setup
+ @old_logger = ActionController::Base.logger
+ Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new)
super
- set_logger
end
- def wait
- ActiveSupport::Notifications.notifier.wait
+ def teardown
+ super
+ Rails::Subscriber.subscribers.clear
+ ActionController::Base.logger = @old_logger
end
+ def set_logger(logger)
+ ActionController::Base.logger = logger
+ end
+
def test_log_with_active_record
get :show
wait
- assert_match /ActiveRecord runtime/, logs[3]
+
+ assert_equal 2, @logger.logged(:info).size
+ assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1]
end
- private
- def set_logger
- @controller.logger = MockLogger.new
- end
+ class SyncSubscriberTest < ActionController::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include ControllerRuntimeSubscriberTest
+ end
- def logs
- @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip}
- end
-end
+ class AsyncSubscriberTest < ActionController::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include ControllerRuntimeSubscriberTest
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index ad744421db..ea82758cf5 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -26,7 +26,7 @@ class Series < ActiveRecord::Base
end
class PolymorphicRoutesTest < ActionController::TestCase
- include ActionController::UrlWriter
+ include ActionController::UrlFor
self.default_url_options[:host] = 'example.com'
def setup
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 65118f9bc9..1510a6a7e0 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -6,6 +6,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up
module Submodule
class ContainedEmptyController < ActionController::Base
end
+
class ContainedNonEmptyController < ActionController::Base
def public_action
render :nothing => true
@@ -20,12 +21,15 @@ module Submodule
end
hide_action :another_hidden_action
end
+
class SubclassedController < ContainedNonEmptyController
hide_action :public_action # Hiding it here should not affect the superclass.
end
end
+
class EmptyController < ActionController::Base
end
+
class NonEmptyController < ActionController::Base
def public_action
render :nothing => true
@@ -37,7 +41,6 @@ class NonEmptyController < ActionController::Base
end
class MethodMissingController < ActionController::Base
-
hide_action :shouldnt_be_called
def shouldnt_be_called
raise "NO WAY!"
@@ -48,16 +51,15 @@ protected
def method_missing(selector)
render :text => selector.to_s
end
-
end
class DefaultUrlOptionsController < ActionController::Base
- def default_url_options_action
- render :nothing => true
+ def from_view
+ render :inline => "<%= #{params[:route]} %>"
end
def default_url_options(options = nil)
- { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' }
+ { :host => 'www.override.com', :action => 'new', :locale => 'en' }
end
end
@@ -68,6 +70,7 @@ class ControllerClassTests < Test::Unit::TestCase
assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
end
+
def test_controller_name
assert_equal 'empty', EmptyController.controller_name
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
@@ -86,41 +89,16 @@ class ControllerInstanceTests < Test::Unit::TestCase
def test_action_methods
@empty_controllers.each do |c|
- hide_mocha_methods_from_controller(c)
assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
end
+
@non_empty_controllers.each do |c|
- hide_mocha_methods_from_controller(c)
assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
end
end
-
- protected
- # Mocha adds some public instance methods to Object that would be
- # considered actions, so explicitly hide_action them.
- def hide_mocha_methods_from_controller(controller)
- mocha_methods = [
- :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object,
- :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher,
- ]
- controller.class.__send__(:hide_action, *mocha_methods)
- end
end
-
class PerformActionTest < ActionController::TestCase
- class MockLogger
- attr_reader :logged
-
- def initialize
- @logged = []
- end
-
- def method_missing(method, *args)
- @logged << args.first.to_s
- end
- end
-
def use_controller(controller_class)
@controller = controller_class.new
@@ -128,9 +106,8 @@ class PerformActionTest < ActionController::TestCase
# a more accurate simulation of what happens in "real life".
@controller.logger = Logger.new(nil)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
rescue_action_in_public!
@@ -145,8 +122,7 @@ class PerformActionTest < ActionController::TestCase
def test_method_missing_is_not_an_action_name
use_controller MethodMissingController
-
- assert ! @controller.__send__(:action_method?, 'method_missing')
+ assert !@controller.__send__(:action_method?, 'method_missing')
get :method_missing
assert_response :success
@@ -172,16 +148,43 @@ class DefaultUrlOptionsTest < ActionController::TestCase
def test_default_url_options_are_used_if_set
with_routing do |set|
set.draw do |map|
- match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options
+ match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
match ':controller/:action'
end
- get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
+ get :from_view, :route => "from_view_url"
- assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
- assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
+ assert_equal 'http://www.override.com/from_view?locale=en', @response.body
+ assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
+ assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
end
end
+
+ def test_default_url_options_are_used_in_non_positional_parameters
+ with_routing do |set|
+ set.draw do |map|
+ scope("/:locale") do
+ resources :descriptions
+ end
+ match ':controller/:action'
+ end
+
+ get :from_view, :route => "description_path(1)"
+
+ assert_equal '/en/descriptions/1', @response.body
+ assert_equal '/en/descriptions', @controller.send(:descriptions_path)
+ assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl")
+ assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl")
+ assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml")
+ assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml")
+ assert_equal '/en/descriptions/1', @controller.send(:description_path, 1)
+ assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1)
+ assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl")
+ assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml")
+ assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml")
+ end
+ end
+
end
class EmptyUrlOptionsTest < ActionController::TestCase
@@ -197,15 +200,12 @@ class EmptyUrlOptionsTest < ActionController::TestCase
get :public_action
assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
end
-end
-class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase
- def test_named_routes_still_work
+ def test_named_routes_with_path_without_doing_a_request_first
with_routing do |set|
set.draw do |map|
resources :things
end
- EmptyController.send :include, ActionController::UrlWriter
assert_equal '/things', EmptyController.new.send(:things_path)
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 679eaf7b38..8a13d1e5f1 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -629,20 +629,6 @@ class FragmentCachingTest < ActionController::TestCase
assert_equal 'generated till now -> fragment content', buffer
end
- def test_fragment_for_logging
- fragment_computed = false
- events = []
- ActiveSupport::Notifications.subscribe { |*args| events << args }
-
- buffer = 'generated till now -> '
- @controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
-
- assert fragment_computed
- assert_equal 'generated till now -> ', buffer
- ActiveSupport::Notifications.notifier.wait
- assert_equal [:exist_fragment?, :write_fragment], events.map(&:first)
- end
-
end
class FunctionalCachingController < ActionController::Base
diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb
index 64f1ad7610..7e19bce3b7 100644
--- a/actionpack/test/controller/dispatcher_test.rb
+++ b/actionpack/test/controller/dispatcher_test.rb
@@ -1,73 +1,59 @@
require 'abstract_unit'
-class DispatcherTest < Test::Unit::TestCase
- Dispatcher = ActionController::Dispatcher
-
- class Foo
- cattr_accessor :a, :b
- end
+# Ensure deprecated dispatcher works
+class DeprecatedDispatcherTest < ActiveSupport::TestCase
+ class DummyApp
+ def call(env)
+ [200, {}, 'response']
+ end
+ end
def setup
- ENV['REQUEST_METHOD'] = 'GET'
-
- # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
ActionDispatch::Callbacks.reset_callbacks(:prepare)
ActionDispatch::Callbacks.reset_callbacks(:call)
-
- ActionController::Routing::Routes.stubs(:call).returns([200, {}, 'response'])
- Dispatcher.stubs(:require_dependency)
end
- def teardown
- ENV.delete 'REQUEST_METHOD'
- end
+ def test_assert_deprecated_to_prepare
+ a = nil
+
+ assert_deprecated do
+ ActionController::Dispatcher.to_prepare { a = 1 }
+ end
- def test_clears_dependencies_after_dispatch_if_in_loading_mode
- ActiveSupport::Dependencies.expects(:clear).once
- dispatch(false)
+ assert_nil a
+ dispatch
+ assert_equal 1, a
end
- def test_prepare_callbacks
- a = b = c = nil
- ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
- ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
- ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
+ def test_assert_deprecated_before_dispatch
+ a = nil
- # Ensure to_prepare callbacks are not run when defined
- assert_nil a || b || c
+ assert_deprecated do
+ ActionController::Dispatcher.before_dispatch { a = 1 }
+ end
- # Run callbacks
+ assert_nil a
dispatch
-
assert_equal 1, a
- assert_equal 2, b
- assert_equal 3, c
-
- # Make sure they are only run once
- a = b = c = nil
- dispatch
- assert_nil a || b || c
end
- def test_to_prepare_with_identifier_replaces
- ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 }
- ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 }
+ def test_assert_deprecated_after_dispatch
+ a = nil
+
+ assert_deprecated do
+ ActionController::Dispatcher.after_dispatch { a = 1 }
+ end
+ assert_nil a
dispatch
- assert_equal 2, Foo.a
- assert_equal nil, Foo.b
+ assert_equal 1, a
end
private
- def dispatch(cache_classes = true)
- ActionController::Dispatcher.prepare_each_request = false
- Dispatcher.define_dispatcher_callbacks(cache_classes)
- @dispatcher ||= ActionDispatch::Callbacks.new(ActionController::Routing::Routes)
- @dispatcher.call({'rack.input' => StringIO.new(''), 'action_dispatch.show_exceptions' => false})
+ def dispatch(cache_classes = true)
+ @dispatcher ||= ActionDispatch::Callbacks.new(DummyApp.new, !cache_classes)
+ @dispatcher.call({'rack.input' => StringIO.new('')})
end
- def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
- assert_equal howmany, klass.subclasses.size, message
- end
end
diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb
index 420ebeacf4..d0635669c2 100644
--- a/actionpack/test/controller/filter_params_test.rb
+++ b/actionpack/test/controller/filter_params_test.rb
@@ -66,18 +66,6 @@ class FilterParamTest < ActionController::TestCase
assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
end
- def test_filter_parameters_inside_logs
- FilterParamController.filter_parameter_logging(:lifo, :amount)
-
- get :payment, :lifo => 'Pratik', :amount => '420', :step => '1'
- ActiveSupport::Notifications.notifier.wait
-
- filtered_params_logs = logs.detect {|l| l =~ /\AParameters/ }
- assert filtered_params_logs.index('"amount"=>"[FILTERED]"')
- assert filtered_params_logs.index('"lifo"=>"[FILTERED]"')
- assert filtered_params_logs.index('"step"=>"1"')
- end
-
private
def set_logger
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index a9b60386f1..85a2e7f44b 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase
end
def test_keep_and_discard_return_values
- flash = ActionController::Flash::FlashHash.new
+ flash = ActionDispatch::Flash::FlashHash.new
flash.update(:foo => :foo_indeed, :bar => :bar_indeed)
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
@@ -187,4 +187,42 @@ class FlashTest < ActionController::TestCase
get :redirect_with_other_flashes
assert_equal "Horses!", @controller.send(:flash)[:joyride]
end
-end \ No newline at end of file
+end
+
+class FlashIntegrationTest < ActionController::IntegrationTest
+ SessionKey = '_myapp_session'
+ SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+
+ class TestController < ActionController::Base
+ def set_flash
+ flash["that"] = "hello"
+ head :ok
+ end
+
+ def use_flash
+ render :inline => "flash: #{flash["that"]}"
+ end
+ end
+
+ def test_flash
+ with_test_route_set do
+ get '/set_flash'
+ assert_response :success
+ assert_equal "hello", @request.flash["that"]
+
+ get '/use_flash'
+ assert_response :success
+ assert_equal "flash: hello", @response.body
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do |map|
+ match ':action', :to => ActionDispatch::Session::CookieStore.new(TestController, :key => SessionKey, :secret => SessionSecret)
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 624b14e69b..683ab5236c 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -254,6 +254,10 @@ class IntegrationProcessTest < ActionController::IntegrationTest
render :text => "Created", :status => 201
end
+ def method
+ render :text => "method: #{request.method}"
+ end
+
def cookie_monster
cookies["cookie_1"] = nil
cookies["cookie_3"] = "chocolate"
@@ -379,6 +383,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest
head '/post'
assert_equal 201, status
assert_equal "", body
+
+ get '/get/method'
+ assert_equal 200, status
+ assert_equal "method: get", body
+
+ head '/get/method'
+ assert_equal 200, status
+ assert_equal "", body
end
end
@@ -391,6 +403,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest
with_routing do |set|
set.draw do |map|
match ':action', :to => ::IntegrationProcessTest::IntegrationController
+ get 'get/:action', :to => ::IntegrationProcessTest::IntegrationController
end
yield
end
diff --git a/actionpack/test/controller/logging_test.rb b/actionpack/test/controller/logging_test.rb
deleted file mode 100644
index 4206dffa7e..0000000000
--- a/actionpack/test/controller/logging_test.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-require 'abstract_unit'
-
-module Another
- class LoggingController < ActionController::Base
- layout "layouts/standard"
-
- def show
- render :nothing => true
- end
-
- def with_layout
- render :template => "test/hello_world", :layout => true
- end
- end
-end
-
-class LoggingTest < ActionController::TestCase
- tests Another::LoggingController
-
- def setup
- super
- set_logger
- end
-
- def get(*args)
- super
- wait
- end
-
- def wait
- ActiveSupport::Notifications.notifier.wait
- end
-
- def test_logging_without_parameters
- get :show
- assert_equal 4, logs.size
- assert_nil logs.detect {|l| l =~ /Parameters/ }
- end
-
- def test_logging_with_parameters
- get :show, :id => '10'
- assert_equal 5, logs.size
-
- params = logs.detect {|l| l =~ /Parameters/ }
- assert_equal 'Parameters: {"id"=>"10"}', params
- end
-
- def test_log_controller_with_namespace_and_action
- get :show
- assert_match /Processed\sAnother::LoggingController#show/, logs[1]
- end
-
- def test_log_view_runtime
- get :show
- assert_match /View runtime/, logs[2]
- end
-
- def test_log_completed_status_and_request_uri
- get :show
- last = logs.last
- assert_match /Completed/, last
- assert_match /200/, last
- assert_match /another\/logging\/show/, last
- end
-
- def test_logger_prints_layout_and_template_rendering_info
- get :with_layout
- logged = logs.find {|l| l =~ /render/i }
- assert_match /Rendered (.*)test\/hello_world.erb within (.*)layouts\/standard.html.erb/, logged
- end
-
- private
- def set_logger
- @controller.logger = MockLogger.new
- end
-
- def logs
- @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip}
- end
-end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 6b9cace9cd..ba2347e4e2 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -461,31 +461,27 @@ end
class RespondWithController < ActionController::Base
respond_to :html, :json
- respond_to :xml, :except => :using_defaults
- respond_to :js, :only => [ :using_defaults, :using_resource ]
+ respond_to :xml, :except => :using_resource_with_block
+ respond_to :js, :only => [ :using_resource_with_block, :using_resource ]
- def using_defaults
- respond_to do |format|
- format.csv { render :text => "CSV" }
- end
+ def using_resource
+ respond_with(resource)
end
- def using_defaults_with_type_list
- respond_to(:js, :xml)
+ def using_resource_with_block
+ respond_with(resource) do |format|
+ format.csv { render :text => "CSV" }
+ end
end
- def default_overwritten
- respond_to do |format|
+ def using_resource_with_overwrite_block
+ respond_with(resource) do |format|
format.html { render :text => "HTML" }
end
end
- def using_resource
- respond_with(Customer.new("david", 13))
- end
-
def using_resource_with_collection
- respond_with([Customer.new("david", 13), Customer.new("jamis", 9)])
+ respond_with([resource, Customer.new("jamis", 9)])
end
def using_resource_with_parent
@@ -493,16 +489,16 @@ class RespondWithController < ActionController::Base
end
def using_resource_with_status_and_location
- respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created)
+ respond_with(resource, :location => "http://test.host/", :status => :created)
end
def using_resource_with_responder
responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
- respond_with(Customer.new("david", 13), :responder => responder)
+ respond_with(resource, :responder => responder)
end
def using_resource_with_action
- respond_with(Customer.new("david", 13), :action => :foo) do |format|
+ respond_with(resource, :action => :foo) do |format|
format.html { raise ActionView::MissingTemplate.new([], "method") }
end
end
@@ -511,11 +507,15 @@ class RespondWithController < ActionController::Base
responder = Class.new(ActionController::Responder) do
def respond; @controller.render :text => "respond #{format}"; end
end
- respond_with(Customer.new("david", 13), :responder => responder)
+ respond_with(resource, :responder => responder)
end
protected
+ def resource
+ Customer.new("david", 13)
+ end
+
def _render_js(js, options)
self.content_type ||= Mime::JS
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
@@ -527,12 +527,18 @@ class InheritedRespondWithController < RespondWithController
respond_to :xml, :json
def index
- respond_with(Customer.new("david", 13)) do |format|
+ respond_with(resource) do |format|
format.json { render :text => "JSON" }
end
end
end
+class EmptyRespondWithController < ActionController::Base
+ def index
+ respond_with(Customer.new("david", 13))
+ end
+end
+
class RespondWithControllerTest < ActionController::TestCase
tests RespondWithController
@@ -547,56 +553,54 @@ class RespondWithControllerTest < ActionController::TestCase
ActionController::Base.use_accept_header = false
end
- def test_using_defaults
+ def test_using_resource
+ @request.accept = "text/javascript"
+ get :using_resource
+ assert_equal "text/javascript", @response.content_type
+ assert_equal '$("body").visualEffect("highlight");', @response.body
+
+ @request.accept = "application/xml"
+ get :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<name>david</name>", @response.body
+
+ @request.accept = "application/json"
+ assert_raise ActionView::MissingTemplate do
+ get :using_resource
+ end
+ end
+
+ def test_using_resource_with_block
@request.accept = "*/*"
- get :using_defaults
+ get :using_resource_with_block
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
@request.accept = "text/csv"
- get :using_defaults
+ get :using_resource_with_block
assert_equal "text/csv", @response.content_type
assert_equal "CSV", @response.body
- @request.accept = "text/javascript"
- get :using_defaults
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
- end
-
- def test_using_defaults_with_type_list
- @request.accept = "*/*"
- get :using_defaults_with_type_list
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
@request.accept = "application/xml"
- get :using_defaults_with_type_list
+ get :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal "<p>Hello world!</p>\n", @response.body
+ assert_equal "<name>david</name>", @response.body
end
- def test_default_overwritten
- get :default_overwritten
+ def test_using_resource_with_overwrite_block
+ get :using_resource_with_overwrite_block
assert_equal "text/html", @response.content_type
assert_equal "HTML", @response.body
end
- def test_using_resource
- @request.accept = "text/javascript"
- get :using_resource
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
+ def test_not_acceptable
@request.accept = "application/xml"
- get :using_resource
- assert_equal "application/xml", @response.content_type
- assert_equal "<name>david</name>", @response.body
+ get :using_resource_with_block
+ assert_equal 406, @response.status
- @request.accept = "application/json"
- assert_raise ActionView::MissingTemplate do
- get :using_resource
- end
+ @request.accept = "text/javascript"
+ get :using_resource_with_overwrite_block
+ assert_equal 406, @response.status
end
def test_using_resource_for_post_with_html_redirects_on_success
@@ -831,22 +835,12 @@ class RespondWithControllerTest < ActionController::TestCase
RespondWithController.responder = ActionController::Responder
end
- def test_not_acceptable
- @request.accept = "application/xml"
- get :using_defaults
- assert_equal 406, @response.status
-
- @request.accept = "text/html"
- get :using_defaults_with_type_list
- assert_equal 406, @response.status
-
- @request.accept = "application/json"
- get :using_defaults_with_type_list
- assert_equal 406, @response.status
-
- @request.accept = "text/javascript"
- get :default_overwritten
- assert_equal 406, @response.status
+ def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called
+ @controller = EmptyRespondWithController.new
+ @request.accept = "*/*"
+ assert_raise RuntimeError do
+ get :index
+ end
end
private
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 1a03396ae9..01ed491732 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -30,16 +30,6 @@ module Backoffice
end
class ResourcesTest < ActionController::TestCase
- # The assertions in these tests are incompatible with the hash method
- # optimisation. This could indicate user level problems
- def setup
- ActionController::Base.optimise_named_routes = false
- end
-
- def teardown
- ActionController::Base.optimise_named_routes = true
- end
-
def test_should_arrange_actions
resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
@@ -304,27 +294,27 @@ class ResourcesTest < ActionController::TestCase
end
end
- def test_member_when_changed_default_restful_actions_and_path_names_not_specified
- default_path_names = ActionController::Base.resources_path_names
- ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'}
-
- with_restful_routing :messages do
- new_options = { :action => 'new', :controller => 'messages' }
- new_path = "/messages/nuevo"
- edit_options = { :action => 'edit', :id => '1', :controller => 'messages' }
- edit_path = "/messages/1/editar"
-
- assert_restful_routes_for :messages do |options|
- assert_recognizes(options.merge(new_options), :path => new_path, :method => :get)
- end
-
- assert_restful_routes_for :messages do |options|
- assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get)
- end
- end
- ensure
- ActionController::Base.resources_path_names = default_path_names
- end
+ # def test_member_when_changed_default_restful_actions_and_path_names_not_specified
+ # default_path_names = ActionController::Base.resources_path_names
+ # ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'}
+ #
+ # with_restful_routing :messages do
+ # new_options = { :action => 'new', :controller => 'messages' }
+ # new_path = "/messages/nuevo"
+ # edit_options = { :action => 'edit', :id => '1', :controller => 'messages' }
+ # edit_path = "/messages/1/editar"
+ #
+ # assert_restful_routes_for :messages do |options|
+ # assert_recognizes(options.merge(new_options), :path => new_path, :method => :get)
+ # end
+ #
+ # assert_restful_routes_for :messages do |options|
+ # assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get)
+ # end
+ # end
+ # ensure
+ # ActionController::Base.resources_path_names = default_path_names
+ # end
def test_with_two_member_actions_with_same_method
[:put, :post].each do |method|
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index c15eaade58..f390bbdc89 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -82,9 +82,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
attr_reader :rs
def setup
- # These tests assume optimisation is on, so re-enable it.
- ActionController::Base.optimise_named_routes = true
-
@rs = ::ActionController::Routing::RouteSet.new
end
@@ -632,7 +629,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
end
def test_routes_changed_correctly_after_clear
- ActionController::Base.optimise_named_routes = true
rs = ::ActionController::Routing::RouteSet.new
rs.draw do |r|
r.connect 'ca', :controller => 'ca', :action => "aa"
diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/subscriber_test.rb
new file mode 100644
index 0000000000..ef1a325799
--- /dev/null
+++ b/actionpack/test/controller/subscriber_test.rb
@@ -0,0 +1,192 @@
+require "abstract_unit"
+require "rails/subscriber/test_helper"
+require "action_controller/railties/subscriber"
+
+module Another
+ class SubscribersController < ActionController::Base
+ def show
+ render :nothing => true
+ end
+
+ def redirector
+ redirect_to "http://foo.bar/"
+ end
+
+ def data_sender
+ send_data "cool data", :filename => "omg.txt"
+ end
+
+ def xfile_sender
+ send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH), :x_sendfile => true
+ end
+
+ def file_sender
+ send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH)
+ end
+
+ def with_fragment_cache
+ render :inline => "<%= cache('foo'){ 'bar' } %>"
+ end
+
+ def with_page_cache
+ cache_page("Super soaker", "/index.html")
+ render :nothing => true
+ end
+ end
+end
+
+module ActionControllerSubscriberTest
+
+ def self.included(base)
+ base.tests Another::SubscribersController
+ end
+
+ def setup
+ @old_logger = ActionController::Base.logger
+
+ @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
+ ActionController::Base.page_cache_directory = @cache_path
+ ActionController::Base.cache_store = :file_store, @cache_path
+
+ Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new)
+ super
+ end
+
+ def teardown
+ super
+ Rails::Subscriber.subscribers.clear
+ FileUtils.rm_rf(@cache_path)
+ ActionController::Base.logger = @old_logger
+ end
+
+ def set_logger(logger)
+ ActionController::Base.logger = logger
+ end
+
+ def test_process_action
+ get :show
+ wait
+ assert_equal 2, logs.size
+ assert_match /Processed\sAnother::SubscribersController#show/, logs[0]
+ end
+
+ def test_process_action_formats
+ get :show
+ wait
+ assert_equal 2, logs.size
+ assert_match /text\/html/, logs[0]
+ end
+
+ def test_process_action_without_parameters
+ get :show
+ wait
+ assert_nil logs.detect {|l| l =~ /Parameters/ }
+ end
+
+ def test_process_action_with_parameters
+ get :show, :id => '10'
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ end
+
+ def test_process_action_with_view_runtime
+ get :show
+ wait
+ assert_match /\(Views: [\d\.]+ms\)/, logs[1]
+ end
+
+ def test_process_action_with_status_and_request_uri
+ get :show
+ wait
+ last = logs.last
+ assert_match /Completed/, last
+ assert_match /200/, last
+ assert_match /another\/subscribers\/show/, last
+ end
+
+ def test_process_action_with_filter_parameters
+ Another::SubscribersController.filter_parameter_logging(:lifo, :amount)
+
+ get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
+ wait
+
+ params = logs[1]
+ assert_match /"amount"=>"\[FILTERED\]"/, params
+ assert_match /"lifo"=>"\[FILTERED\]"/, params
+ assert_match /"step"=>"1"/, params
+ end
+
+ def test_redirect_to
+ get :redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to http://foo.bar/", logs[0]
+ end
+
+ def test_send_data
+ get :data_sender
+ wait
+
+ assert_equal 3, logs.size
+ assert_match /Sent data omg\.txt/, logs[0]
+ end
+
+ def test_send_file
+ get :file_sender
+ wait
+
+ assert_equal 3, logs.size
+ assert_match /Sent file/, logs[0]
+ assert_match /test\/fixtures\/company\.rb/, logs[0]
+ end
+
+ def test_send_xfile
+ get :xfile_sender
+ wait
+
+ assert_equal 3, logs.size
+ assert_match /Sent X\-Sendfile header/, logs[0]
+ assert_match /test\/fixtures\/company\.rb/, logs[0]
+ end
+
+ def test_with_fragment_cache
+ ActionController::Base.perform_caching = true
+ get :with_fragment_cache
+ wait
+
+ assert_equal 4, logs.size
+ assert_match /Exist fragment\? views\/foo/, logs[0]
+ assert_match /Write fragment views\/foo/, logs[1]
+ ensure
+ ActionController::Base.perform_caching = true
+ end
+
+ def test_with_page_cache
+ ActionController::Base.perform_caching = true
+ get :with_page_cache
+ wait
+
+ assert_equal 3, logs.size
+ assert_match /Write page/, logs[0]
+ assert_match /\/index\.html/, logs[0]
+ ensure
+ ActionController::Base.perform_caching = true
+ end
+
+ def logs
+ @logs ||= @logger.logged(:info)
+ end
+
+ class SyncSubscriberTest < ActionController::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include ActionControllerSubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActionController::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include ActionControllerSubscriberTest
+ end
+end
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 428f40b9f8..c2b8cd85d8 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -100,286 +100,3 @@ class UrlRewriterTests < ActionController::TestCase
end
end
-class UrlWriterTests < ActionController::TestCase
- class W
- include ActionController::UrlWriter
- end
-
- def teardown
- W.default_url_options.clear
- end
-
- def add_host!
- W.default_url_options[:host] = 'www.basecamphq.com'
- end
-
- def test_exception_is_thrown_without_host
- assert_raise RuntimeError do
- W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
- end
- end
-
- def test_anchor
- assert_equal('/c/a#anchor',
- W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
- )
- end
-
- def test_anchor_should_call_to_param
- assert_equal('/c/a#anchor',
- W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
- )
- end
-
- def test_anchor_should_be_cgi_escaped
- assert_equal('/c/a#anc%2Fhor',
- W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
- )
- end
-
- def test_default_host
- add_host!
- assert_equal('http://www.basecamphq.com/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
- )
- end
-
- def test_host_may_be_overridden
- add_host!
- assert_equal('http://37signals.basecamphq.com/c/a/i',
- W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
- )
- end
-
- def test_port
- add_host!
- assert_equal('http://www.basecamphq.com:3000/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
- )
- end
-
- def test_protocol
- add_host!
- assert_equal('https://www.basecamphq.com/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
- )
- end
-
- def test_protocol_with_and_without_separator
- add_host!
- assert_equal('https://www.basecamphq.com/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
- )
- assert_equal('https://www.basecamphq.com/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
- )
- end
-
- def test_trailing_slash
- add_host!
- options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
- assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
- end
-
- def test_trailing_slash_with_protocol
- add_host!
- options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'}
- assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
- assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'}))
- end
-
- def test_trailing_slash_with_only_path
- options = {:controller => 'foo', :trailing_slash => true}
- assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true}))
- options.update({:action => 'bar', :id => '33'})
- assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true}))
- assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true}))
- end
-
- def test_trailing_slash_with_anchor
- options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'}
- assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options)
- assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'}))
- end
-
- def test_trailing_slash_with_params
- url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link')
- params = extract_params(url)
- assert_equal params[0], { :p1 => 'cafe' }.to_query
- assert_equal params[1], { :p2 => 'link' }.to_query
- end
-
- def test_relative_url_root_is_respected
- orig_relative_url_root = ActionController::Base.relative_url_root
- ActionController::Base.relative_url_root = '/subdir'
-
- add_host!
- assert_equal('https://www.basecamphq.com/subdir/c/a/i',
- W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
- )
- ensure
- ActionController::Base.relative_url_root = orig_relative_url_root
- end
-
- def test_named_routes
- with_routing do |set|
- set.draw do |map|
- match 'this/is/verbose', :to => 'home#index', :as => :no_args
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
- end
-
- # We need to create a new class in order to install the new named route.
- kls = Class.new { include ActionController::UrlWriter }
- controller = kls.new
- assert controller.respond_to?(:home_url)
- assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
- controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
-
- assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
- assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
- assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
- end
- end
-
- def test_relative_url_root_is_respected_for_named_routes
- orig_relative_url_root = ActionController::Base.relative_url_root
- ActionController::Base.relative_url_root = '/subdir'
-
- with_routing do |set|
- set.draw do |map|
- match '/home/sweet/home/:user', :to => 'home#index', :as => :home
- end
-
- kls = Class.new { include ActionController::UrlWriter }
- controller = kls.new
-
- assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
- controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
- end
- ensure
- ActionController::Base.relative_url_root = orig_relative_url_root
- end
-
- def test_only_path
- with_routing do |set|
- set.draw do |map|
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
- match ':controller/:action/:id'
- end
-
- # We need to create a new class in order to install the new named route.
- kls = Class.new { include ActionController::UrlWriter }
- controller = kls.new
- assert controller.respond_to?(:home_url)
- assert_equal '/brave/new/world',
- controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
-
- assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
- assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
- end
- end
-
- def test_one_parameter
- assert_equal('/c/a?param=val',
- W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
- )
- end
-
- def test_two_parameters
- url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
- params = extract_params(url)
- assert_equal params[0], { :p1 => 'X1' }.to_query
- assert_equal params[1], { :p2 => 'Y2' }.to_query
- end
-
- def test_hash_parameter
- url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
- params = extract_params(url)
- assert_equal params[0], { 'query[category]' => 'prof' }.to_query
- assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
- end
-
- def test_array_parameter
- url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
- params = extract_params(url)
- assert_equal params[0], { 'query[]' => 'Bob' }.to_query
- assert_equal params[1], { 'query[]' => 'prof' }.to_query
- end
-
- def test_hash_recursive_parameters
- url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
- params = extract_params(url)
- assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
- assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
- assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
- end
-
- def test_hash_recursive_and_array_parameters
- url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
- assert_match %r(^/c/a/101), url
- params = extract_params(url)
- assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
- assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
- assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
- assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
- end
-
- def test_path_generation_for_symbol_parameter_keys
- assert_generates("/image", :controller=> :image)
- end
-
- def test_named_routes_with_nil_keys
- with_routing do |set|
- set.draw do |map|
- match 'posts.:format', :to => 'posts#index', :as => :posts
- match '/', :to => 'posts#index', :as => :main
- end
-
- # We need to create a new class in order to install the new named route.
- kls = Class.new { include ActionController::UrlWriter }
- kls.default_url_options[:host] = 'www.basecamphq.com'
-
- controller = kls.new
- params = {:action => :index, :controller => :posts, :format => :xml}
- assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
- params[:format] = nil
- assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
- end
- end
-
- def test_formatted_url_methods_are_deprecated
- with_routing do |set|
- set.draw do |map|
- resources :posts
- end
- # We need to create a new class in order to install the new named route.
- kls = Class.new { include ActionController::UrlWriter }
- controller = kls.new
- params = {:id => 1, :format => :xml}
- assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params))
- end
- assert_deprecated do
- assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml))
- end
- end
- end
-
- def test_multiple_includes_maintain_distinct_options
- first_class = Class.new { include ActionController::UrlWriter }
- second_class = Class.new { include ActionController::UrlWriter }
-
- first_host, second_host = 'firsthost.com', 'secondhost.com'
-
- first_class.default_url_options[:host] = first_host
- second_class.default_url_options[:host] = second_host
-
- assert_equal first_class.default_url_options[:host], first_host
- assert_equal second_class.default_url_options[:host], second_host
- end
-
- private
- def extract_params(url)
- url.split('?', 2).last.split('&').sort
- end
-end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
new file mode 100644
index 0000000000..f3ea5209f4
--- /dev/null
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -0,0 +1,107 @@
+require 'abstract_unit'
+
+class DispatcherTest < Test::Unit::TestCase
+ class Foo
+ cattr_accessor :a, :b
+ end
+
+ class DummyApp
+ def call(env)
+ [200, {}, 'response']
+ end
+ end
+
+ def setup
+ Foo.a, Foo.b = 0, 0
+ ActionDispatch::Callbacks.reset_callbacks(:prepare)
+ ActionDispatch::Callbacks.reset_callbacks(:call)
+ end
+
+ def test_prepare_callbacks_with_cache_classes
+ a = b = c = nil
+ ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
+ ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
+ ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ dispatch
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+
+ # Make sure they are only run once
+ a = b = c = nil
+ dispatch
+ assert_nil a || b || c
+ end
+
+ def test_prepare_callbacks_without_cache_classes
+ a = b = c = nil
+ ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
+ ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
+ ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ dispatch(false)
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+
+ # Make sure they are run again
+ a = b = c = nil
+ dispatch(false)
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+ end
+
+ def test_to_prepare_with_identifier_replaces
+ ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 }
+ ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 }
+
+ dispatch
+ assert_equal 2, Foo.a
+ assert_equal 0, Foo.b
+ end
+
+ def test_before_and_after_callbacks
+ ActionDispatch::Callbacks.before { |*args| Foo.a += 1; Foo.b += 1 }
+ ActionDispatch::Callbacks.after { |*args| Foo.a += 1; Foo.b += 1 }
+
+ dispatch
+ assert_equal 2, Foo.a
+ assert_equal 2, Foo.b
+
+ dispatch
+ assert_equal 4, Foo.a
+ assert_equal 4, Foo.b
+ end
+
+ def test_should_send_an_instrumentation_callback_for_async_processing
+ ActiveSupport::Notifications.expects(:instrument).with("action_dispatch.callback")
+ dispatch
+ end
+
+ def test_should_send_an_instrumentation_callback_for_async_processing_even_on_failure
+ ActiveSupport::Notifications.notifier.expects(:publish)
+ assert_raise RuntimeError do
+ dispatch { |env| raise "OMG" }
+ end
+ end
+
+ private
+
+ def dispatch(cache_classes = true, &block)
+ @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new, !cache_classes)
+ @dispatcher.call({'rack.input' => StringIO.new('')})
+ end
+
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index b62df9a6b2..cb95ecea50 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -319,7 +319,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "allow method hacking on post" do
- [:get, :head, :options, :put, :post, :delete].each do |method|
+ [:get, :options, :put, :post, :delete].each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == :head ? :get : method, request.method)
end
@@ -341,7 +341,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "head masquerading as get" do
- request = stub_request 'REQUEST_METHOD' => 'HEAD'
+ request = stub_request 'REQUEST_METHOD' => 'GET', "rack.methodoverride.original_method" => "HEAD"
assert_equal :get, request.method
assert request.get?
assert request.head?
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index c4b0b9cdbf..6dccabdb3f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -22,6 +22,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
delete 'logout' => :destroy, :as => :logout
end
+ resource :session do
+ get :create
+ end
+
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
@@ -29,6 +33,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match 'account/modulo/:name', :to => redirect("/%{name}s")
match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }
+ match 'account/google' => redirect('http://www.google.com/')
match 'openid/login', :via => [:get, :post], :to => "openid#login"
@@ -47,6 +52,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'admin/accounts' => "queenbee#accounts"
end
+ scope 'es' do
+ resources :projects, :path_names => { :edit => 'cambiar' }, :as => 'projeto'
+ end
+
resources :projects, :controller => :project do
resources :involvements, :attachments
@@ -56,7 +65,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :companies do
resources :people
- resource :avatar
+ resource :avatar, :controller => :avatar
end
resources :images do
@@ -65,7 +74,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :people do
nested do
- namespace ":access_token" do
+ scope "/:access_token" do
resource :avatar
end
end
@@ -88,6 +97,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ resources :replies do
+ member do
+ put :answer, :to => :mark_as_answer
+ delete :answer, :to => :unmark_as_answer
+ end
+ end
+
+ resources :posts, :only => [:index, :show]
+
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
match 'people/:id/update', :to => 'people#update', :as => :update_person
@@ -97,7 +115,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
namespace :account do
+ match 'description', :to => "account#description", :as => "description"
resource :subscription, :credit, :credit_card
+
+ namespace :admin do
+ resource :subscription
+ end
+ end
+
+ namespace :forum do
+ resources :products, :as => '' do
+ resources :questions
+ end
end
controller :articles do
@@ -112,6 +141,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :rooms
end
+ scope '(:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+
+ namespace :admin do
+ scope '(/:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+ end
+
match '/info' => 'projects#info', :as => 'info'
root :to => 'projects#index'
@@ -165,6 +204,31 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_session_singleton_resource
+ with_test_routes do
+ get '/session'
+ assert_equal 'sessions#create', @response.body
+ assert_equal '/session', session_path
+
+ post '/session'
+ assert_equal 'sessions#create', @response.body
+
+ put '/session'
+ assert_equal 'sessions#update', @response.body
+
+ delete '/session'
+ assert_equal 'sessions#destroy', @response.body
+
+ get '/session/new'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/session/new', new_session_path
+
+ get '/session/edit'
+ assert_equal 'sessions#edit', @response.body
+ assert_equal '/session/edit', edit_session_path
+ end
+ end
+
def test_redirect_modulo
with_test_routes do
get '/account/modulo/name'
@@ -193,20 +257,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- # TODO: rackmount is broken
- # def test_admin
- # with_test_routes do
- # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- # assert_equal 'queenbee#index', @response.body
- #
- # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
- #
- # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- # assert_equal 'queenbee#accounts', @response.body
- #
- # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
- # end
- # end
+ def test_admin
+ with_test_routes do
+ get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#index', @response.body
+
+ assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#accounts', @response.body
+
+ assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} }
+ end
+ end
def test_global
with_test_routes do
@@ -231,31 +294,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_projects
with_test_routes do
get '/projects'
- assert_equal 'projects#index', @response.body
+ assert_equal 'project#index', @response.body
assert_equal '/projects', projects_path
+ post '/projects'
+ assert_equal 'project#create', @response.body
+
get '/projects.xml'
- assert_equal 'projects#index', @response.body
+ assert_equal 'project#index', @response.body
assert_equal '/projects.xml', projects_path(:format => 'xml')
get '/projects/new'
- assert_equal 'projects#new', @response.body
+ assert_equal 'project#new', @response.body
assert_equal '/projects/new', new_project_path
get '/projects/new.xml'
- assert_equal 'projects#new', @response.body
+ assert_equal 'project#new', @response.body
assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
get '/projects/1'
- assert_equal 'projects#show', @response.body
+ assert_equal 'project#show', @response.body
assert_equal '/projects/1', project_path(:id => '1')
get '/projects/1.xml'
- assert_equal 'projects#show', @response.body
+ assert_equal 'project#show', @response.body
assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
get '/projects/1/edit'
- assert_equal 'projects#edit', @response.body
+ assert_equal 'project#edit', @response.body
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
end
@@ -317,7 +383,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
get '/projects/1/companies/1/avatar'
- assert_equal 'avatars#show', @response.body
+ assert_equal 'avatar#show', @response.body
assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
end
end
@@ -394,6 +460,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_replies
+ with_test_routes do
+ put '/replies/1/answer'
+ assert_equal 'replies#mark_as_answer', @response.body
+
+ delete '/replies/1/answer'
+ assert_equal 'replies#unmark_as_answer', @response.body
+ end
+ end
+
+ def test_posts
+ with_test_routes do
+ get '/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/posts', posts_path
+
+ get '/posts/1'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/1', post_path(:id => 1)
+
+ assert_raise(ActionController::RoutingError) { post '/posts' }
+ assert_raise(ActionController::RoutingError) { put '/posts/1' }
+ assert_raise(ActionController::RoutingError) { delete '/posts/1' }
+ end
+ end
+
+ def test_path_names
+ with_test_routes do
+ get '/es/projeto'
+ assert_equal 'projects#index', @response.body
+
+ get '/es/projeto/1/cambiar'
+ assert_equal 'projects#edit', @response.body
+ end
+ end
+
def test_sprockets
with_test_routes do
get '/sprockets.js'
@@ -419,6 +521,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_forum_products
+ with_test_routes do
+ get '/forum'
+ assert_equal 'forum/products#index', @response.body
+ assert_equal '/forum', forum_products_path
+
+ get '/forum/basecamp'
+ assert_equal 'forum/products#show', @response.body
+ assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
+
+ get '/forum/basecamp/questions'
+ assert_equal 'forum/questions#index', @response.body
+ assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
+
+ get '/forum/basecamp/questions/1'
+ assert_equal 'forum/questions#show', @response.body
+ assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
+ end
+ end
+
def test_articles_perma
with_test_routes do
get '/articles/2009/08/18/rails-3'
@@ -431,13 +553,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_account_namespace
with_test_routes do
get '/account/subscription'
- assert_equal 'subscriptions#show', @response.body
+ assert_equal 'account/subscriptions#show', @response.body
+ assert_equal '/account/subscription', account_subscription_path
get '/account/credit'
- assert_equal 'credits#show', @response.body
+ assert_equal 'account/credits#show', @response.body
+ assert_equal '/account/credit', account_credit_path
get '/account/credit_card'
- assert_equal 'credit_cards#show', @response.body
+ assert_equal 'account/credit_cards#show', @response.body
+ assert_equal '/account/credit_card', account_credit_card_path
+ end
+ end
+
+ def test_nested_namespace
+ with_test_routes do
+ get '/account/admin/subscription'
+ assert_equal 'account/admin/subscriptions#show', @response.body
+ assert_equal '/account/admin/subscription', account_admin_subscription_path
end
end
@@ -472,7 +605,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#index', @response.body
end
end
-
+
def test_index
with_test_routes do
assert_equal '/info', info_path
@@ -488,7 +621,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#info', @response.body
end
end
-
+
def test_convention_match_with_no_scope
with_test_routes do
assert_equal '/account/overview', account_overview_path
@@ -497,6 +630,78 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_redirect_with_complete_url
+ with_test_routes do
+ get '/account/google'
+ assert_equal 301, @response.status
+ assert_equal 'http://www.google.com/', @response.headers['Location']
+ assert_equal 'Moved Permanently', @response.body
+ end
+ end
+
+ def test_redirect_with_port
+ previous_host, self.host = self.host, 'www.example.com:3000'
+ with_test_routes do
+ get '/account/login'
+ assert_equal 301, @response.status
+ assert_equal 'http://www.example.com:3000/login', @response.headers['Location']
+ assert_equal 'Moved Permanently', @response.body
+ end
+ ensure
+ self.host = previous_host
+ end
+
+ def test_normalize_namespaced_matches
+ with_test_routes do
+ assert_equal '/account/description', account_description_path
+
+ get '/account/description'
+ assert_equal 'account#description', @response.body
+ end
+ end
+
+ def test_optional_scoped_path
+ with_test_routes do
+ assert_equal '/en/descriptions', descriptions_path("en")
+ assert_equal '/descriptions', descriptions_path(nil)
+ assert_equal '/en/descriptions/1', description_path("en", 1)
+ assert_equal '/descriptions/1', description_path(nil, 1)
+
+ get '/en/descriptions'
+ assert_equal 'descriptions#index', @response.body
+
+ get '/descriptions'
+ assert_equal 'descriptions#index', @response.body
+
+ get '/en/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
+
+ get '/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
+ end
+ end
+
+ def test_nested_optional_scoped_path
+ with_test_routes do
+ assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
+ assert_equal '/admin/descriptions', admin_descriptions_path(nil)
+ assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
+ assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
+
+ get '/admin/en/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
+
+ get '/admin/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
+
+ get '/admin/en/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
+
+ get '/admin/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
+ end
+ end
+
private
def with_test_routes
real_routes, temp_routes = ActionController::Routing::Routes, Routes
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 9f6a93756c..951fb4a22e 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -104,4 +104,27 @@ class ShowExceptionsTest < ActionController::IntegrationTest
assert_response 405
assert_match /ActionController::MethodNotAllowed/, body
end
+
+ test "publishes notifications" do
+ # Wait pending notifications to be published
+ ActiveSupport::Notifications.notifier.wait
+
+ @app, event = ProductionApp, nil
+ self.remote_addr = '127.0.0.1'
+
+ ActiveSupport::Notifications.subscribe('action_dispatch.show_exception') do |*args|
+ event = args
+ end
+
+ get "/"
+ assert_response 500
+ assert_match /puke/, body
+
+ ActiveSupport::Notifications.notifier.wait
+
+ assert_equal 'action_dispatch.show_exception', event.first
+ assert_kind_of Hash, event.last[:env]
+ assert_equal 'GET', event.last[:env]["REQUEST_METHOD"]
+ assert_kind_of RuntimeError, event.last[:exception]
+ end
end
diff --git a/actionpack/test/dispatch/string_coercion_test.rb b/actionpack/test/dispatch/string_coercion_test.rb
deleted file mode 100644
index d79b17b932..0000000000
--- a/actionpack/test/dispatch/string_coercion_test.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'abstract_unit'
-
-class StringCoercionTest < ActiveSupport::TestCase
- test "body responds to each" do
- original_body = []
- body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
-
- assert original_body.respond_to?(:each)
- assert body.respond_to?(:each)
- end
-
- test "body responds to to_path" do
- original_body = []
- def original_body.to_path; end
- body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
-
- assert original_body.respond_to?(:to_path)
- assert body.respond_to?(:to_path)
- end
-
- test "body does not responds to to_path" do
- original_body = []
- body = ActionDispatch::StringCoercion::UglyBody.new(original_body)
-
- assert !original_body.respond_to?(:to_path)
- assert !body.respond_to?(:to_path)
- end
-
- test "calls to_s on body parts" do
- app = lambda { |env|
- [200, {'Content-Type' => 'html'}, [1, 2, 3]]
- }
- app = ActionDispatch::StringCoercion.new(app)
- parts = []
- status, headers, body = app.call({})
- body.each { |part| parts << part }
-
- assert_equal %w( 1 2 3 ), parts
- end
-end
diff --git a/actionpack/test/fixtures/respond_with/using_defaults.js.rjs b/actionpack/test/fixtures/respond_with/using_defaults.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_with/using_defaults.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs b/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder b/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder
deleted file mode 100644
index 598d62e2fc..0000000000
--- a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder
+++ /dev/null
@@ -1 +0,0 @@
-xml.p "Hello world!" \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_defaults.html.erb b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb
index 6769dd60bd..6769dd60bd 100644
--- a/actionpack/test/fixtures/respond_with/using_defaults.html.erb
+++ b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index b0e5d7a94c..7346cb22bc 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -69,7 +69,7 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost
attr_accessor :author
def author_attributes=(attributes); end
- attr_accessor :comments
+ attr_accessor :comments, :comment_ids
def comments_attributes=(attributes); end
attr_accessor :tags
diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb
index 2465444fc5..4eb2f262bd 100644
--- a/actionpack/test/template/active_model_helper_i18n_test.rb
+++ b/actionpack/test/template/active_model_helper_i18n_test.rb
@@ -16,27 +16,27 @@ class ActiveModelHelperI18nTest < Test::Unit::TestCase
stubs(:content_tag).returns 'content_tag'
- I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
- I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
+ I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
end
def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
end
def test_error_messages_for_given_no_header_option_it_translates_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message'
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message'
error_messages_for(:object => @object, :locale => 'en')
end
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never
error_messages_for(:object => @object, :message => 'message', :locale => 'en')
end
def test_error_messages_for_given_no_message_option_it_translates_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:'
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
error_messages_for(:object => @object, :locale => 'en')
end
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 9fb2080f77..fb51b67185 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -2475,6 +2475,28 @@ class DateHelperTest < ActionView::TestCase
}, options)
end
+ def test_select_html_safety
+ assert select_day(16).html_safe?
+ assert select_month(8).html_safe?
+ assert select_year(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
+ assert select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
+ assert select_second(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
+
+ assert select_minute(8, :use_hidden => true).html_safe?
+ assert select_month(8, :prompt => 'Choose month').html_safe?
+
+ assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector').html_safe?
+ assert select_date(Time.mktime(2003, 8, 16), :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]").html_safe?
+ end
+
+ def test_object_select_html_safety
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ assert date_select("post", "written_on", :default => Time.local(2006, 9, 19, 15, 16, 35), :include_blank => true).html_safe?
+ assert time_select("post", "written_on", :ignore_date => true).html_safe?
+ end
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index b1e9fe99a2..0c5c5d17ee 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -16,8 +16,8 @@ class FormHelperTest < ActionView::TestCase
}
}
},
- :views => {
- :labels => {
+ :helpers => {
+ :label => {
:post => {
:body => "Write entire text here"
}
@@ -25,6 +25,20 @@ class FormHelperTest < ActionView::TestCase
}
}
+ # Create "submit" locale for testing I18n submit helpers
+ I18n.backend.store_translations 'submit', {
+ :helpers => {
+ :submit => {
+ :create => 'Create {{model}}',
+ :update => 'Confirm {{model}} changes',
+ :submit => 'Save changes',
+ :another_post => {
+ :update => 'Update your {{model}}'
+ }
+ }
+ }
+ }
+
@post = Post.new
@comment = Comment.new
def @post.errors()
@@ -190,6 +204,11 @@ class FormHelperTest < ActionView::TestCase
hidden_field("post", "title", :value => "Something Else")
end
+ def test_text_field_with_custom_type
+ assert_dom_equal '<input id="user_email" size="30" name="user[email]" type="email" />',
+ text_field("user", "email", :type => "email")
+ end
+
def test_check_box
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
@@ -234,6 +253,19 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_check_box_with_multiple_behavior
+ @post.comment_ids = [2,3]
+ assert_dom_equal(
+ '<input name="post[comment_ids][]" type="hidden" value="0" /><input id="post_comment_ids_1" name="post[comment_ids][]" type="checkbox" value="1" />',
+ check_box("post", "comment_ids", { :multiple => true }, 1)
+ )
+ assert_dom_equal(
+ '<input name="post[comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_comment_ids_3" name="post[comment_ids][]" type="checkbox" value="3" />',
+ check_box("post", "comment_ids", { :multiple => true }, 3)
+ )
+ end
+
+
def test_checkbox_disabled_still_submits_checked_value
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="1" /><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
@@ -475,6 +507,67 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_submit_with_object_as_new_record_and_locale_strings
+ old_locale, I18n.locale = I18n.locale, :submit
+
+ def @post.new_record?() true; end
+ form_for(:post, @post) do |f|
+ concat f.submit
+ end
+
+ expected = "<form action='http://www.example.com' method='post'>" +
+ "<input name='commit' id='post_submit' type='submit' value='Create Post' />" +
+ "</form>"
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_submit_with_object_as_existing_record_and_locale_strings
+ old_locale, I18n.locale = I18n.locale, :submit
+
+ form_for(:post, @post) do |f|
+ concat f.submit
+ end
+
+ expected = "<form action='http://www.example.com' method='post'>" +
+ "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" +
+ "</form>"
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_submit_without_object_and_locale_strings
+ old_locale, I18n.locale = I18n.locale, :submit
+
+ form_for(:post) do |f|
+ concat f.submit :class => "extra"
+ end
+
+ expected = "<form action='http://www.example.com' method='post'>" +
+ "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />" +
+ "</form>"
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_submit_with_object_and_nested_lookup
+ old_locale, I18n.locale = I18n.locale, :submit
+
+ form_for(:another_post, @post) do |f|
+ concat f.submit
+ end
+
+ expected = "<form action='http://www.example.com' method='post'>" +
+ "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" +
+ "</form>"
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_nested_fields_for
form_for(:post, @post) do |f|
f.fields_for(:comment, @post) do |c|
@@ -659,7 +752,7 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
-
+
def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement
@post.author = Author.new(321)
@@ -670,7 +763,7 @@ class FormHelperTest < ActionView::TestCase
concat af.text_field(:name)
end
end
-
+
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
@@ -715,7 +808,7 @@ class FormHelperTest < ActionView::TestCase
end
end
end
-
+
expected = '<form action="http://www.example.com" method="post">' +
'<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb
index 91e370efa7..4972ea6511 100644
--- a/actionpack/test/template/form_options_helper_i18n_test.rb
+++ b/actionpack/test/template/form_options_helper_i18n_test.rb
@@ -6,7 +6,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase
def setup
@prompt_message = 'Select!'
I18n.backend.send(:init_translations)
- I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } }
+ I18n.backend.store_translations :en, :helpers => { :select => { :prompt => @prompt_message } }
end
def teardown
@@ -14,7 +14,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase
end
def test_select_with_prompt_true_translates_prompt_message
- I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' })
+ I18n.expects(:translate).with('helpers.select.prompt', { :default => 'Please select' })
select('post', 'category', [], :prompt => true)
end
@@ -24,4 +24,4 @@ class FormOptionsHelperI18nTests < ActionView::TestCase
select('post', 'category', [], :prompt => true)
)
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index f0f686f6e2..03caad3d46 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -13,8 +13,13 @@ class JavaScriptHelperTest < ActionView::TestCase
def setup
super
+ ActiveSupport.escape_html_entities_in_json = true
@template = self
end
+
+ def teardown
+ ActiveSupport.escape_html_entities_in_json = false
+ end
def _evaluate_assigns_and_ivars() end
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index 313a769088..9225153798 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -317,6 +317,11 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
def setup
super
@generator = create_generator
+ ActiveSupport.escape_html_entities_in_json = true
+ end
+
+ def teardown
+ ActiveSupport.escape_html_entities_in_json = false
end
def _evaluate_assigns_and_ivars() end
diff --git a/actionpack/test/template/subscriber_test.rb b/actionpack/test/template/subscriber_test.rb
new file mode 100644
index 0000000000..af0b3102cf
--- /dev/null
+++ b/actionpack/test/template/subscriber_test.rb
@@ -0,0 +1,102 @@
+require "abstract_unit"
+require "rails/subscriber/test_helper"
+require "action_view/railties/subscriber"
+require "controller/fake_models"
+
+module ActionViewSubscriberTest
+
+ def setup
+ @old_logger = ActionController::Base.logger
+ @view = ActionView::Base.new(ActionController::Base.view_paths, {})
+ Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH))
+ Rails::Subscriber.add(:action_view, ActionView::Railties::Subscriber.new)
+ super
+ end
+
+ def teardown
+ super
+ Rails::Subscriber.subscribers.clear
+ ActionController::Base.logger = @old_logger
+ end
+
+ def set_logger(logger)
+ ActionController::Base.logger = logger
+ end
+
+ def test_render_file_template
+ @view.render(:file => "test/hello_world.erb")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered test\/hello_world\.erb/, @logger.logged(:info).last
+ end
+
+ def test_render_text_template
+ @view.render(:text => "OMG")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered text template/, @logger.logged(:info).last
+ end
+
+ def test_render_inline_template
+ @view.render(:inline => "<%= 'OMG' %>")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered inline template/, @logger.logged(:info).last
+ end
+
+ def test_render_partial_template
+ @view.render(:partial => "test/customer")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered test\/_customer.erb/, @logger.logged(:info).last
+ end
+
+ def test_render_partial_with_implicit_path
+ @view.stubs(:controller_path).returns("test")
+ @view.render(Customer.new("david"), :greeting => "hi")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last
+ end
+
+ def test_render_collection_template
+ @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ])
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered test\/_customer.erb/, @logger.logged(:info).last
+ end
+
+ def test_render_collection_with_implicit_path
+ @view.stubs(:controller_path).returns("test")
+ @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last
+ end
+
+ def test_render_collection_template_without_path
+ @view.stubs(:controller_path).returns("test")
+ @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi")
+ wait
+
+ assert_equal 1, @logger.logged(:info).size
+ assert_match /Rendered collection/, @logger.logged(:info).last
+ end
+
+ class SyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include ActionViewSubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include ActionViewSubscriberTest
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 08143ba680..088c07b8bb 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -360,6 +360,20 @@ class TextHelperTest < ActionView::TestCase
assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>")
end
+ def test_auto_link_other_protocols
+ silence_warnings do
+ begin
+ old_re_value = ActionView::Helpers::TextHelper::AUTO_LINK_RE
+ ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, %r{(ftp://)[^\s<]+}
+ link_raw = 'ftp://example.com/file.txt'
+ link_result = generate_result(link_raw)
+ assert_equal %(Download #{link_result}), auto_link("Download #{link_raw}")
+ ensure
+ ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, old_re_value
+ end
+ end
+ end
+
def test_auto_link_already_linked
linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com')
linked2 = generate_result('www.rubyonrails.com', 'http://www.rubyonrails.com')
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 26500568ee..7489c0daa5 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH]
+
* Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
Example :
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index f14016027c..6eab00c177 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,10 +30,12 @@ module ActiveModel
extend ActiveSupport::Autoload
autoload :AttributeMethods
+ autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
autoload :Conversion
autoload :DeprecatedErrorMethods
autoload :Dirty
+ autoload :EachValidator, 'active_model/validator'
autoload :Errors
autoload :Lint
autoload :Name, 'active_model/naming'
@@ -42,12 +44,11 @@ module ActiveModel
autoload :Observing
autoload :Serialization
autoload :StateMachine
+ autoload :TestCase
autoload :Translation
+ autoload :VERSION
autoload :Validations
autoload :Validator
- autoload :EachValidator, 'active_model/validator'
- autoload :BlockValidator, 'active_model/validator'
- autoload :VERSION
module Serializers
extend ActiveSupport::Autoload
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index abc084a74b..0b6c75c46e 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -105,8 +105,7 @@ module ActiveModel
else
attr_name = attribute.to_s.gsub('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
- options = { :default => "{{attribute}} {{message}}", :attribute => attr_name,
- :scope => @base.class.i18n_scope }
+ options = { :default => "{{attribute}} {{message}}", :attribute => attr_name }
messages.each do |m|
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
@@ -153,7 +152,7 @@ module ActiveModel
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value,
- :scope => [@base.class.i18n_scope, :errors]
+ :scope => [:errors]
}.merge(options)
I18n.translate(key, options)
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 1cdb897f13..ea58021767 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -1,27 +1,26 @@
en:
- activemodel:
- errors:
- # model.errors.full_messages format.
- format: "{{attribute}} {{message}}"
+ errors:
+ # The default format use in full error messages.
+ format: "{{attribute}} {{message}}"
- # The values :model, :attribute and :value are always available for interpolation
- # The value :count is available when applicable. Can be used for pluralization.
- messages:
- inclusion: "is not included in the list"
- exclusion: "is reserved"
- invalid: "is invalid"
- confirmation: "doesn't match confirmation"
- accepted: "must be accepted"
- empty: "can't be empty"
- blank: "can't be blank"
- too_long: "is too long (maximum is {{count}} characters)"
- too_short: "is too short (minimum is {{count}} characters)"
- wrong_length: "is the wrong length (should be {{count}} characters)"
- not_a_number: "is not a number"
- greater_than: "must be greater than {{count}}"
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
- equal_to: "must be equal to {{count}}"
- less_than: "must be less than {{count}}"
- less_than_or_equal_to: "must be less than or equal to {{count}}"
- odd: "must be odd"
- even: "must be even"
+ # The values :model, :attribute and :value are always available for interpolation
+ # The value :count is available when applicable. Can be used for pluralization.
+ messages:
+ inclusion: "is not included in the list"
+ exclusion: "is reserved"
+ invalid: "is invalid"
+ confirmation: "doesn't match confirmation"
+ accepted: "must be accepted"
+ empty: "can't be empty"
+ blank: "can't be blank"
+ too_long: "is too long (maximum is {{count}} characters)"
+ too_short: "is too short (minimum is {{count}} characters)"
+ wrong_length: "is the wrong length (should be {{count}} characters)"
+ not_a_number: "is not a number"
+ greater_than: "must be greater than {{count}}"
+ greater_than_or_equal_to: "must be greater than or equal to {{count}}"
+ equal_to: "must be equal to {{count}}"
+ less_than: "must be less than {{count}}"
+ less_than_or_equal_to: "must be less than or equal to {{count}}"
+ odd: "must be odd"
+ even: "must be even"
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index ee6d48bfc6..794de7dc55 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,19 +10,17 @@ module ActiveModel
included do
extend ActiveModel::Naming
- cattr_accessor :include_root_in_json, :instance_writer => false
+ cattr_accessor :include_root_in_json, :instance_writer => true
end
# Returns a JSON string representing the model. Some configuration is
# available through +options+.
#
- # The option <tt>ActiveRecord::Base.include_root_in_json</tt> controls the
- # top-level behavior of to_json. In a new Rails application, it is set to
- # <tt>true</tt> in initializers/new_rails_defaults.rb. When it is <tt>true</tt>,
+ # The option <tt>ActiveModel::Base.include_root_in_json</tt> controls the
+ # top-level behavior of to_json. It is true by default. When it is <tt>true</tt>,
# to_json will emit a single root node named after the object's type. For example:
#
# konata = User.find(1)
- # ActiveRecord::Base.include_root_in_json = true
# konata.to_json
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
# "created_at": "2006/08/01", "awesome": true} }
diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb
index 4cb5c9cbc0..6328807ad7 100644
--- a/activemodel/lib/active_model/test_case.rb
+++ b/activemodel/lib/active_model/test_case.rb
@@ -1,5 +1,3 @@
-require "active_support/test_case"
-
module ActiveModel #:nodoc:
class TestCase < ActiveSupport::TestCase #:nodoc:
def with_kcode(kcode)
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index e5ef1e6114..2d2df269d0 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -25,13 +25,14 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
defaults = lookup_ancestors.map do |klass|
- :"#{klass.model_name.underscore}.#{attribute}"
+ :"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}"
end
+ defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
defaults << attribute.to_s.humanize
- options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults
+ options.reverse_merge! :count => 1, :default => defaults
I18n.translate(defaults.shift, options)
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index d5460a58bd..276472ea46 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -15,21 +15,26 @@ module ActiveModel
module ClassMethods
# Validates each attribute against a block.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Validations
+ #
# validates_each :first_name, :last_name do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value[0] == ?z
# end
# end
#
# Options:
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>,
+ # other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_each(*attr_names, &block)
options = attr_names.extract_options!.symbolize_keys
@@ -42,7 +47,9 @@ module ActiveModel
#
# This can be done with a symbol pointing to a method:
#
- # class Comment < ActiveRecord::Base
+ # class Comment
+ # include ActiveModel::Validations
+ #
# validate :must_be_friends
#
# def must_be_friends
@@ -52,7 +59,9 @@ module ActiveModel
#
# Or with a block which is passed the current record to be validated:
#
- # class Comment < ActiveRecord::Base
+ # class Comment
+ # include ActiveModel::Validations
+ #
# validate do |comment|
# comment.must_be_friends
# end
@@ -71,6 +80,13 @@ module ActiveModel
end
set_callback(:validate, *args, &block)
end
+
+ private
+
+ def _merge_attributes(attr_names)
+ options = attr_names.extract_options!
+ options.merge(:attributes => attr_names)
+ end
end
# Returns the Errors object that holds all information about attribute error messages.
@@ -90,27 +106,22 @@ module ActiveModel
!valid?
end
- protected
- # Hook method defining how an attribute value should be retieved. By default this is assumed
- # to be an instance named after the attribute. Override this method in subclasses should you
- # need to retrieve the value for a given attribute differently e.g.
- # class MyClass
- # include ActiveModel::Validations
- #
- # def initialize(data = {})
- # @data = data
- # end
- #
- # private
- #
- # def read_attribute_for_validation(key)
- # @data[key]
- # end
- # end
- #
- def read_attribute_for_validation(key)
- send(key)
- end
+ # Hook method defining how an attribute value should be retieved. By default this is assumed
+ # to be an instance named after the attribute. Override this method in subclasses should you
+ # need to retrieve the value for a given attribute differently e.g.
+ # class MyClass
+ # include ActiveModel::Validations
+ #
+ # def initialize(data = {})
+ # @data = data
+ # end
+ #
+ # def read_attribute_for_validation(key)
+ # @data[key]
+ # end
+ # end
+ #
+ alias :read_attribute_for_validation :send
end
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index bd9463ed27..0423fcd17f 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -10,6 +10,13 @@ module ActiveModel
record.errors.add(attribute, :accepted, :default => options[:message])
end
end
+
+ def setup(klass)
+ # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
+ # as instance_methods returns symbols unlike 1.8 which returns strings.
+ new_attributes = attributes.reject { |name| klass.instance_methods.map(&:to_s).include?("#{name}=") }
+ klass.send(:attr_accessor, *new_attributes)
+ end
end
module ClassMethods
@@ -37,18 +44,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
- options = attr_names.extract_options!
-
- db_cols = begin
- column_names
- rescue Exception # To ignore both statement and connection errors
- []
- end
-
- names = attr_names.reject { |name| db_cols.include?(name.to_s) }
- attr_accessor(*names)
-
- validates_with AcceptanceValidator, options.merge(:attributes => attr_names)
+ validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index b06effdceb..8041d4b61f 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -6,6 +6,10 @@ module ActiveModel
return if confirmed.nil? || value == confirmed
record.errors.add(attribute, :confirmation, :default => options[:message])
end
+
+ def setup(klass)
+ klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
+ end
end
module ClassMethods
@@ -38,9 +42,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
- options = attr_names.extract_options!
- attr_accessor(*(attr_names.map { |n| :"#{n}_confirmation" }))
- validates_with ConfirmationValidator, options.merge(:attributes => attr_names)
+ validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index f8759f253b..7ee718cf3c 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -33,9 +33,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
- options = attr_names.extract_options!
- options[:in] ||= options.delete(:within)
- validates_with ExclusionValidator, options.merge(:attributes => attr_names)
+ validates_with ExclusionValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index d5427c2b03..9a9e7eca4d 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -8,6 +8,20 @@ module ActiveModel
record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
end
end
+
+ def check_validity!
+ unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
+ raise ArgumentError, "Either :with or :without must be supplied (but not both)"
+ end
+
+ if options[:with] && !options[:with].is_a?(Regexp)
+ raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
+ end
+
+ if options[:without] && !options[:without].is_a?(Regexp)
+ raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
+ end
+ end
end
module ClassMethods
@@ -43,21 +57,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
- options = attr_names.extract_options!
-
- unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
- raise ArgumentError, "Either :with or :without must be supplied (but not both)"
- end
-
- if options[:with] && !options[:with].is_a?(Regexp)
- raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
- end
-
- if options[:without] && !options[:without].is_a?(Regexp)
- raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
- end
-
- validates_with FormatValidator, options.merge(:attributes => attr_names)
+ validates_with FormatValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index a122e9e737..0c1334fe1b 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -33,9 +33,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
- options = attr_names.extract_options!
- options[:in] ||= options.delete(:within)
- validates_with InclusionValidator, options.merge(:attributes => attr_names)
+ validates_with InclusionValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 5db2060fe5..9ceb75487f 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,36 +1,43 @@
module ActiveModel
module Validations
class LengthValidator < EachValidator
- OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
- attr_reader :type
def initialize(options)
- @type = (OPTIONS & options.keys).first
+ if range = (options.delete(:in) || options.delete(:within))
+ raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
+ options[:minimum], options[:maximum] = range.begin, range.end
+ options[:maximum] -= 1 if range.exclude_end?
+ end
+
super(options.reverse_merge(:tokenizer => DEFAULT_TOKENIZER))
end
def check_validity!
- ensure_one_range_option!
- ensure_argument_types!
+ keys = CHECKS.keys & options.keys
+
+ if keys.empty?
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ end
+
+ keys.each do |key|
+ value = options[key]
+
+ unless value.is_a?(Integer) && value >= 0
+ raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ end
+ end
end
def validate_each(record, attribute, value)
- checks = options.slice(:minimum, :maximum, :is)
- value = options[:tokenizer].call(value) if value.kind_of?(String)
-
- if [:within, :in].include?(type)
- range = options[type]
- checks[:minimum], checks[:maximum] = range.begin, range.end
- checks[:maximum] -= 1 if range.exclude_end?
- end
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
- checks.each do |key, check_value|
+ CHECKS.each do |key, validity_check|
+ next unless check_value = options[key]
custom_message = options[:message] || options[MESSAGES[key]]
- validity_check = CHECKS[key]
valid_value = if key == :maximum
value.nil? || value.size.send(validity_check, check_value)
@@ -38,33 +45,8 @@ module ActiveModel
value && value.size.send(validity_check, check_value)
end
- record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value) unless valid_value
- end
- end
-
- protected
-
- def ensure_one_range_option! #:nodoc:
- range_options = OPTIONS & options.keys
-
- case range_options.size
- when 0
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
- when 1
- # Valid number of options; do nothing.
- else
- raise ArgumentError, 'Too many range options specified. Choose only one.'
- end
- end
-
- def ensure_argument_types! #:nodoc:
- value = options[type]
-
- case type
- when :within, :in
- raise ArgumentError, ":#{type} must be a Range" unless value.is_a?(Range)
- when :is, :minimum, :maximum
- raise ArgumentError, ":#{type} must be a nonnegative Integer" unless value.is_a?(Integer) && value >= 0
+ next if valid_value
+ record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value)
end
end
end
@@ -107,8 +89,7 @@ module ActiveModel
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attr_names)
- options = attr_names.extract_options!
- validates_with LengthValidator, options.merge(:attributes => attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
end
alias_method :validates_size_of, :validates_length_of
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index f2aab8c5b8..c6d84c5312 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -10,9 +10,10 @@ module ActiveModel
end
def check_validity!
- options.slice(*CHECKS.keys) do |option, value|
- next if [:odd, :even].include?(option)
- raise ArgumentError, ":#{option} must be a number, a symbol or a proc" unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
+ keys = CHECKS.keys - [:odd, :even]
+ options.slice(*keys).each do |option, value|
+ next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
+ raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
end
end
@@ -103,8 +104,7 @@ module ActiveModel
# end
#
def validates_numericality_of(*attr_names)
- options = attr_names.extract_options!
- validates_with NumericalityValidator, options.merge(:attributes => attr_names)
+ validates_with NumericalityValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index a4c6f866a7..4a71cf79b5 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -34,8 +34,7 @@ module ActiveModel
# The method, proc or string should return or evaluate to a true or false value.
#
def validates_presence_of(*attr_names)
- options = attr_names.extract_options!
- validates_with PresenceValidator, options.merge(:attributes => attr_names)
+ validates_with PresenceValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
new file mode 100644
index 0000000000..4c82a993ae
--- /dev/null
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -0,0 +1,106 @@
+module ActiveModel
+ module Validations
+ module ClassMethods
+ # This method is a shortcut to all default validators and any custom
+ # validator classes ending in 'Validator'. Note that Rails default
+ # validators can be overridden inside specific classes by creating
+ # custom validator classes in their place such as PresenceValidator.
+ #
+ # Examples of using the default rails validators:
+ #
+ # validates :terms, :acceptance => true
+ # validates :password, :confirmation => true
+ # validates :username, :exclusion => { :in => %w(admin superuser) }
+ # validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }
+ # validates :age, :inclusion => { :in => 0..9 }
+ # validates :first_name, :length => { :maximum => 30 }
+ # validates :age, :numericality => true
+ # validates :username, :presence => true
+ # validates :username, :uniqueness => true
+ #
+ # The power of the +validates+ method comes when using cusom validators
+ # and default validators in one call for a given attribute e.g.
+ #
+ # class EmailValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors[attribute] << (options[:message] || "is not an email") unless
+ # value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
+ # end
+ # end
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # attr_accessor :name, :email
+ #
+ # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
+ # validates :email, :presence => true, :email => true
+ # end
+ #
+ # Validator classes my also exist within the class being validated
+ # allowing custom modules of validators to be included as needed e.g.
+ #
+ # class Film
+ # include ActiveModel::Validations
+ #
+ # class TitleValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i
+ # end
+ # end
+ #
+ # validates :name, :title => true
+ # end
+ #
+ # The validators hash can also handle regular expressions, ranges and arrays:
+ #
+ # validates :email, :format => /@/
+ # validates :gender, :inclusion => %w(male female)
+ # validates :password, :length => 6..20
+ #
+ # Finally, the options :if, :unless, :on, :allow_blank and :allow_nil can be given
+ # to one specific validator:
+ #
+ # validates :password, :presence => { :if => :password_required? }, :confirmation => true
+ #
+ # Or to all at the same time:
+ #
+ # validates :password, :presence => true, :confirmation => true, :if => :password_required?
+ #
+ def validates(*attributes)
+ defaults = attributes.extract_options!
+ validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil)
+
+ raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
+ raise ArgumentError, "Attribute names must be symbols" if attributes.any?{ |attribute| !attribute.is_a?(Symbol) }
+ raise ArgumentError, "You need to supply at least one validation" if validations.empty?
+
+ defaults.merge!(:attributes => attributes)
+
+ validations.each do |key, options|
+ begin
+ validator = const_get("#{key.to_s.camelize}Validator")
+ rescue NameError
+ raise ArgumentError, "Unknown validator: '#{key}'"
+ end
+
+ validates_with(validator, defaults.merge(_parse_validates_options(options)))
+ end
+ end
+
+ protected
+
+ def _parse_validates_options(options) #:nodoc:
+ case options
+ when TrueClass
+ {}
+ when Hash
+ options
+ when Regexp
+ { :with => options }
+ when Range, Array
+ { :in => options }
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 8d521173c6..db563876af 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -2,14 +2,16 @@ module ActiveModel
module Validations
module ClassMethods
- # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.
+ # Passes the record off to the class or classes specified and allows them
+ # to add errors based on more complex conditions.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Validations
# validates_with MyValidator
# end
#
- # class MyValidator < ActiveRecord::Validator
- # def validate
+ # class MyValidator < ActiveModel::Validator
+ # def validate(record)
# if some_complex_logic
# record.errors[:base] << "This record is invalid"
# end
@@ -23,37 +25,46 @@ module ActiveModel
#
# You may also pass it multiple classes, like so:
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Validations
# validates_with MyValidator, MyOtherValidator, :on => :create
# end
#
# Configuration options:
- # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt>
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # * <tt>on</tt> - Specifies when this validation is active
+ # (<tt>:create</tt> or <tt>:update</tt>
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # * <tt>unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur
+ # (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
#
- # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>:
+ # If you pass any additional configuration options, they will be passed
+ # to the class and available as <tt>options</tt>:
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Validations
# validates_with MyValidator, :my_custom_key => "my custom value"
# end
#
- # class MyValidator < ActiveRecord::Validator
- # def validate
+ # class MyValidator < ActiveModel::Validator
+ # def validate(record)
# options[:my_custom_key] # => "my custom value"
# end
# end
#
def validates_with(*args, &block)
options = args.extract_options!
- args.each { |klass| validate(klass.new(options, &block), options) }
+ args.each do |klass|
+ validator = klass.new(options, &block)
+ validator.setup(self) if validator.respond_to?(:setup)
+ validate(validator, options)
+ end
end
end
end
-end
-
-
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 01695cb73a..382a4cc98d 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,12 +1,13 @@
module ActiveModel #:nodoc:
- # A simple base class that can be used along with ActiveModel::Base.validates_with
+ # A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with
#
- # class Person < ActiveModel::Base
+ # class Person
+ # include ActiveModel::Validations
# validates_with MyValidator
# end
#
# class MyValidator < ActiveModel::Validator
- # def validate
+ # def validate(record)
# if some_complex_logic
# record.errors[:base] = "This record is invalid"
# end
@@ -18,10 +19,11 @@ module ActiveModel #:nodoc:
# end
# end
#
- # Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>,
- # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>.
+ # Any class that inherits from ActiveModel::Validator must implement a method
+ # called <tt>validate</tt> which accepts a <tt>record</tt>.
#
- # class Person < ActiveModel::Base
+ # class Person
+ # include ActiveModel::Validations
# validates_with MyValidator
# end
#
@@ -36,7 +38,7 @@ module ActiveModel #:nodoc:
# from within the validators message
#
# class MyValidator < ActiveModel::Validator
- # def validate
+ # def validate(record)
# record.errors[:base] << "This is some custom error message"
# record.errors[:first_name] << "This is some complex validation"
# # etc...
@@ -51,13 +53,47 @@ module ActiveModel #:nodoc:
# @my_custom_field = options[:field_name] || :first_name
# end
# end
+ #
+ # The easiest way to add custom validators for validating individual attributes
+ # is with the convenient ActiveModel::EachValidator for example:
+ #
+ # class TitleValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value)
+ # end
+ # end
+ #
+ # This can now be used in combination with the +validates+ method
+ # (see ActiveModel::Validations::ClassMethods.validates for more on this)
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # attr_accessor :title
+ #
+ # validates :title, :presence => true, :title => true
+ # end
+ #
+ # Validator may also define a +setup+ instance method which will get called
+ # with the class that using that validator as it's argument. This can be
+ # useful when there are prerequisites such as an attr_accessor being present
+ # for example:
+ #
+ # class MyValidator < ActiveModel::Validator
+ # def setup(klass)
+ # klass.send :attr_accessor, :custom_attribute
+ # end
+ # end
+ #
class Validator
attr_reader :options
+ # Accepts options that will be made availible through the +options+ reader.
def initialize(options)
@options = options
end
+ # Override this method in subclasses with validation logic, adding errors
+ # to the records +errors+ array where necessary.
def validate(record)
raise NotImplementedError
end
@@ -70,7 +106,10 @@ module ActiveModel #:nodoc:
# All ActiveModel validations are built on top of this Validator.
class EachValidator < Validator
attr_reader :attributes
-
+
+ # Returns a new validator instance. All options will be available via the
+ # +options+ reader, however the <tt>:attributes</tt> option will be removed
+ # and instead be made available through the +attributes+ reader.
def initialize(options)
@attributes = Array(options.delete(:attributes))
raise ":attributes cannot be blank" if @attributes.empty?
@@ -78,18 +117,26 @@ module ActiveModel #:nodoc:
check_validity!
end
+ # Performs validation on the supplied record. By default this will call
+ # +validates_each+ to determine validity therefore subclasses should
+ # override +validates_each+ with validation logic.
def validate(record)
attributes.each do |attribute|
- value = record.send(:read_attribute_for_validation, attribute)
+ value = record.read_attribute_for_validation(attribute)
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
validate_each(record, attribute, value)
end
end
+ # Override this method in subclasses with the validation logic, adding
+ # errors to the records +errors+ array where necessary.
def validate_each(record, attribute, value)
raise NotImplementedError
end
+ # Hook method that gets called by the initializer allowing verification
+ # that the arguments supplied are valid. You could for example raise an
+ # ArgumentError when invalid options are supplied.
def check_validity!
end
end
@@ -103,6 +150,8 @@ module ActiveModel #:nodoc:
super
end
+ private
+
def validate_each(record, attribute, value)
@block.call(record, attribute, value)
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 30193956ea..917bb720d0 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -8,7 +8,6 @@ $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
require 'config'
require 'active_model'
-require 'active_model/test_case'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activemodel/test/cases/tests_database.rb b/activemodel/test/cases/tests_database.rb
index 79668dd941..8ca54d2678 100644
--- a/activemodel/test/cases/tests_database.rb
+++ b/activemodel/test/cases/tests_database.rb
@@ -2,8 +2,6 @@ require 'logger'
$:.unshift(File.dirname(__FILE__) + '/../../../activerecord/lib')
require 'active_record'
-require 'active_record/test_case'
-require 'active_record/fixtures'
module ActiveModel
module TestsDatabase
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index bfc1ca12e6..e25d308ca1 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -11,6 +11,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
assert_equal 'person name attribute', Person.human_attribute_name('name')
end
+
+ def test_translated_model_attributes_with_default
+ I18n.backend.store_translations 'en', :attributes => { :name => 'name default attribute' }
+ assert_equal 'name default attribute', Person.human_attribute_name('name')
+ end
def test_translated_model_attributes_with_symbols
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 54b2405c92..6116ef71d4 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -7,42 +7,6 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
def setup
Person.reset_callbacks(:validate)
@person = Person.new
-
- @old_load_path, @old_backend = I18n.load_path, I18n.backend
- I18n.load_path.clear
- I18n.backend = I18n::Backend::Simple.new
-
- I18n.backend.store_translations :'en', {
- :activemodel => {
- :errors => {
- :messages => {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is {{count}} characters)",
- :too_short => "is too short (minimum is {{count}} characters)",
- :wrong_length => "is the wrong length (should be {{count}} characters)",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than {{count}}",
- :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
- :equal_to => "must be equal to {{count}}",
- :less_than => "must be less than {{count}}",
- :less_than_or_equal_to => "must be less than or equal to {{count}}",
- :odd => "must be odd",
- :even => "must be even"
- }
- }
- }
- }
- end
-
- def teardown
- I18n.load_path.replace @old_load_path
- I18n.backend = @old_backend
end
# validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index a7656fe219..7d33fcea98 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -9,10 +9,10 @@ class I18nValidationTest < ActiveModel::TestCase
Person.reset_callbacks(:validate)
@person = Person.new
- @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations('en', :activemodel => {:errors => {:messages => {:custom => nil}}})
+ I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}})
end
def teardown
@@ -42,13 +42,13 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
- @person.errors.add('name', 'empty')
- I18n.expects(:translate).with(:"person.name", :default => ['Name', 'Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name')
- @person.errors.full_messages
+ @person.errors.add(:name, 'not found')
+ Person.expects(:human_attribute_name).with(:name, :default => 'Name').returns("Person's name")
+ assert_equal ["Person's name not found"], @person.errors.full_messages
end
def test_errors_full_messages_uses_format
- I18n.backend.store_translations('en', :activemodel => {:errors => {:format => "Field {{attribute}} {{message}}"}})
+ I18n.backend.store_translations('en', :errors => {:format => "Field {{attribute}} {{message}}"})
@person.errors.add('name', 'empty')
assert_equal ["Field Name empty"], @person.errors.full_messages
end
@@ -254,8 +254,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_confirmation_of w/o mocha
def test_validates_confirmation_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}}
Person.validates_confirmation_of :title
@person.title_confirmation = 'foo'
@@ -264,7 +264,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_confirmation_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}}
Person.validates_confirmation_of :title
@person.title_confirmation = 'foo'
@@ -275,8 +275,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_acceptance_of w/o mocha
def test_validates_acceptance_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}}
Person.validates_acceptance_of :title, :allow_nil => false
@person.valid?
@@ -284,7 +284,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_acceptance_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}}
Person.validates_acceptance_of :title, :allow_nil => false
@person.valid?
@@ -294,8 +294,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_presence_of w/o mocha
def test_validates_presence_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}}
Person.validates_presence_of :title
@person.valid?
@@ -303,7 +303,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_presence_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}}
Person.validates_presence_of :title
@person.valid?
@@ -313,8 +313,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_length_of :within w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}}
Person.validates_length_of :title, :within => 3..5
@person.valid?
@@ -322,7 +322,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}}
Person.validates_length_of :title, :within => 3..5
@person.valid?
@@ -332,8 +332,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_length_of :is w/o mocha
def test_validates_length_of_is_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}}
Person.validates_length_of :title, :is => 5
@person.valid?
@@ -341,7 +341,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_is_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}}
Person.validates_length_of :title, :is => 5
@person.valid?
@@ -351,8 +351,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_format_of w/o mocha
def test_validates_format_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
@person.valid?
@@ -360,7 +360,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
@person.valid?
@@ -370,8 +370,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_inclusion_of w/o mocha
def test_validates_inclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}}
Person.validates_inclusion_of :title, :in => %w(a b c)
@person.valid?
@@ -379,7 +379,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}}
Person.validates_inclusion_of :title, :in => %w(a b c)
@person.valid?
@@ -389,8 +389,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_exclusion_of w/o mocha
def test_validates_exclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}}
Person.validates_exclusion_of :title, :in => %w(a b c)
@person.title = 'a'
@@ -399,7 +399,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_exclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}}
Person.validates_exclusion_of :title, :in => %w(a b c)
@person.title = 'a'
@@ -410,8 +410,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of without :only_integer w/o mocha
def test_validates_numericality_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title
@person.title = 'a'
@@ -420,7 +420,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true
@person.title = 'a'
@@ -431,8 +431,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of with :only_integer w/o mocha
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true
@person.title = 'a'
@@ -441,7 +441,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true
@person.title = 'a'
@@ -452,8 +452,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of :odd w/o mocha
def test_validates_numericality_of_odd_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :odd => true
@person.title = 0
@@ -462,7 +462,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_odd_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :odd => true
@person.title = 0
@@ -473,8 +473,8 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of :less_than w/o mocha
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
@person.title = 1
@@ -483,7 +483,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_of_less_than_finds_global_default_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
@person.title = 1
@@ -494,7 +494,7 @@ class I18nValidationTest < ActiveModel::TestCase
# test with validates_with
def test_validations_with_message_symbol_must_translate
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:custom_error => "I am a custom error"}}
Person.validates_presence_of :title, :message => :custom_error
@person.title = nil
@person.valid?
@@ -502,7 +502,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_with_message_symbol_must_translate_per_attribute
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}
Person.validates_presence_of :title, :message => :custom_error
@person.title = nil
@person.valid?
@@ -510,7 +510,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_with_message_symbol_must_translate_per_model
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:person => {:custom_error => "I am a custom error"}}}
Person.validates_presence_of :title, :message => :custom_error
@person.title = nil
@person.valid?
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index f3ef5e648a..99d0268b67 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -221,10 +221,8 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_nasty_params
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
- assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>-6) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6) }
assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 75cd654f98..38b3f87e93 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -154,6 +154,14 @@ class NumericalityValidationTest < ActiveModel::TestCase
Person.reset_callbacks(:validate)
end
+ def test_validates_numericality_with_invalid_args
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than_or_equal_to => "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than => "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than => "foo" }
+ assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :equal_to => "foo" }
+ end
+
private
def invalid!(values, error = nil)
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
new file mode 100644
index 0000000000..d15fb4a524
--- /dev/null
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -0,0 +1,114 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'models/person'
+require 'models/person_with_validator'
+require 'validators/email_validator'
+
+class ValidatesTest < ActiveModel::TestCase
+ setup :reset_callbacks
+ teardown :reset_callbacks
+
+ def reset_callbacks
+ Person.reset_callbacks(:validate)
+ PersonWithValidator.reset_callbacks(:validate)
+ end
+
+ def test_validates_with_built_in_validation
+ Person.validates :title, :numericality => true
+ person = Person.new
+ person.valid?
+ assert_equal ['is not a number'], person.errors[:title]
+ end
+
+ def test_validates_with_built_in_validation_and_options
+ Person.validates :salary, :numericality => { :message => 'my custom message' }
+ person = Person.new
+ person.valid?
+ assert_equal ['my custom message'], person.errors[:salary]
+ end
+
+ def test_validates_with_validator_class
+ Person.validates :karma, :email => true
+ person = Person.new
+ person.valid?
+ assert_equal ['is not an email'], person.errors[:karma]
+ end
+
+ def test_validates_with_if_as_local_conditions
+ Person.validates :karma, :presence => true, :email => { :unless => :condition_is_true }
+ person = Person.new
+ person.valid?
+ assert_equal ["can't be blank"], person.errors[:karma]
+ end
+
+ def test_validates_with_if_as_shared_conditions
+ Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true
+ person = Person.new
+ person.valid?
+ assert ["can't be blank", "is not an email"], person.errors[:karma].sort
+ end
+
+ def test_validates_with_unless_shared_conditions
+ Person.validates :karma, :presence => true, :email => true, :unless => :condition_is_true
+ person = Person.new
+ assert person.valid?
+ end
+
+ def test_validates_with_allow_nil_shared_conditions
+ Person.validates :karma, :length => { :minimum => 20 }, :email => true, :allow_nil => true
+ person = Person.new
+ assert person.valid?
+ end
+
+ def test_validates_with_regexp
+ Person.validates :karma, :format => /positive|negative/
+ person = Person.new
+ assert person.invalid?
+ assert_equal ['is invalid'], person.errors[:karma]
+ person.karma = "positive"
+ assert person.valid?
+ end
+
+ def test_validates_with_array
+ Person.validates :gender, :inclusion => %w(m f)
+ person = Person.new
+ assert person.invalid?
+ assert_equal ['is not included in the list'], person.errors[:gender]
+ person.gender = "m"
+ assert person.valid?
+ end
+
+ def test_validates_with_range
+ Person.validates :karma, :length => 6..20
+ person = Person.new
+ assert person.invalid?
+ assert_equal ['is too short (minimum is 6 characters)'], person.errors[:karma]
+ person.karma = 'something'
+ assert person.valid?
+ end
+
+ def test_validates_with_validator_class_and_options
+ Person.validates :karma, :email => { :message => 'my custom message' }
+ person = Person.new
+ person.valid?
+ assert_equal ['my custom message'], person.errors[:karma]
+ end
+
+ def test_validates_with_unknown_validator
+ assert_raise(ArgumentError) { Person.validates :karma, :unknown => true }
+ end
+
+ def test_validates_with_included_validator
+ PersonWithValidator.validates :title, :presence => true
+ person = PersonWithValidator.new
+ person.valid?
+ assert_equal ['Local validator'], person.errors[:title]
+ end
+
+ def test_validates_with_included_validator_and_options
+ PersonWithValidator.validates :title, :presence => { :custom => ' please' }
+ person = PersonWithValidator.new
+ person.valid?
+ assert_equal ['Local validator please'], person.errors[:title]
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 7540ccb580..66b072ea38 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -120,6 +120,28 @@ class ValidatesWithTest < ActiveRecord::TestCase
Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
assert topic.valid?
end
+
+ test "calls setup method of validator passing in self when validator has setup method" do
+ topic = Topic.new
+ validator = stub_everything
+ validator.stubs(:new).returns(validator)
+ validator.stubs(:validate)
+ validator.stubs(:respond_to?).with(:setup).returns(true)
+ validator.expects(:setup).with(Topic).once
+ Topic.validates_with(validator)
+ assert topic.valid?
+ end
+
+ test "doesn't call setup method of validator when validator has no setup method" do
+ topic = Topic.new
+ validator = stub_everything
+ validator.stubs(:new).returns(validator)
+ validator.stubs(:validate)
+ validator.stubs(:respond_to?).with(:setup).returns(false)
+ validator.expects(:setup).with(Topic).never
+ Topic.validates_with(validator)
+ assert topic.valid?
+ end
test "validates_with with options" do
Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
diff --git a/activemodel/test/models/custom_reader.rb b/activemodel/test/models/custom_reader.rb
index 7ac70e6167..14a8be9ebc 100644
--- a/activemodel/test/models/custom_reader.rb
+++ b/activemodel/test/models/custom_reader.rb
@@ -8,8 +8,6 @@ class CustomReader
def []=(key, value)
@data[key] = value
end
-
- private
def read_attribute_for_validation(key)
@data[key]
diff --git a/activemodel/test/models/person.rb b/activemodel/test/models/person.rb
index c83d768379..cf16a38618 100644
--- a/activemodel/test/models/person.rb
+++ b/activemodel/test/models/person.rb
@@ -2,7 +2,11 @@ class Person
include ActiveModel::Validations
extend ActiveModel::Translation
- attr_accessor :title, :karma, :salary
+ attr_accessor :title, :karma, :salary, :gender
+
+ def condition_is_true
+ true
+ end
end
class Child < Person
diff --git a/activemodel/test/models/person_with_validator.rb b/activemodel/test/models/person_with_validator.rb
new file mode 100644
index 0000000000..f9763ea853
--- /dev/null
+++ b/activemodel/test/models/person_with_validator.rb
@@ -0,0 +1,11 @@
+class PersonWithValidator
+ include ActiveModel::Validations
+
+ class PresenceValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank?
+ end
+ end
+
+ attr_accessor :title, :karma
+end
diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb
new file mode 100644
index 0000000000..cff47ac230
--- /dev/null
+++ b/activemodel/test/validators/email_validator.rb
@@ -0,0 +1,6 @@
+class EmailValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ record.errors[attribute] << (options[:message] || "is not an email") unless
+ value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
+ end
+end \ No newline at end of file
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 0cfd8cdc87..38bcf0c787 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH]
+
* Add Relation#except. [Pratik Naik]
one_red_item = Item.where(:colour => 'red').limit(1)
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 728dec8925..d5b6d40514 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -134,6 +134,9 @@ module ActiveRecord
autoload :AbstractAdapter
end
end
+
+ autoload :TestCase
+ autoload :TestFixtures, 'active_record/fixtures'
end
Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 9f7b2a60b2..a43c4d09d6 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -188,11 +188,11 @@ module ActiveRecord
conditions << append_conditions(reflection, preload_options)
associated_records = reflection.klass.with_exclusive_scope do
- reflection.klass.find(:all, :conditions => [conditions, ids],
- :include => options[:include],
- :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
- :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
- :order => options[:order])
+ reflection.klass.where([conditions, ids]).
+ includes(options[:include]).
+ joins("INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}").
+ select("#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id").
+ order(options[:order]).to_a
end
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
end
@@ -327,6 +327,7 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
column_type = klass.columns.detect{|c| c.name == primary_key}.type
+
ids = id_map.keys.map do |id|
if column_type == :integer
id.to_i
@@ -336,15 +337,14 @@ module ActiveRecord
id
end
end
+
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
+
associated_records = klass.with_exclusive_scope do
- klass.find(:all, :conditions => [conditions, ids],
- :include => options[:include],
- :select => options[:select],
- :joins => options[:joins],
- :order => options[:order])
+ klass.where([conditions, ids]).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
end
+
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
end
end
@@ -363,13 +363,12 @@ module ActiveRecord
conditions << append_conditions(reflection, preload_options)
reflection.klass.with_exclusive_scope do
- reflection.klass.find(:all,
- :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
- :include => preload_options[:include] || options[:include],
- :conditions => [conditions, ids],
- :joins => options[:joins],
- :group => preload_options[:group] || options[:group],
- :order => preload_options[:order] || options[:order])
+ reflection.klass.select(preload_options[:select] || options[:select] || "#{table_name}.*").
+ includes(preload_options[:include] || options[:include]).
+ where([conditions, ids]).
+ joins(options[:joins]).
+ group(preload_options[:group] || options[:group]).
+ order(preload_options[:order] || options[:order])
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 149a11eb47..468a6cd9f8 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1701,24 +1701,30 @@ module ActiveRecord
end
def construct_finder_arel_with_included_associations(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
+ relation = relation.joins(options[:joins]).
select(column_aliases(join_dependency)).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- where(construct_conditions(options[:conditions], scope)).
- from((scope && scope[:from]) || options[:from])
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ where(options[:conditions]).
+ from(options[:from])
+
+ scoped_relation = current_scoped_methods
+ scoped_relation_limit = scoped_relation.taken if scoped_relation
+
+ relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods
- relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
- relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections)
+ if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit])
+ relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
+ end
+
+ relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections)
relation
end
@@ -1746,29 +1752,29 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
- where(construct_conditions(options[:conditions], scope)).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- limit(construct_limit(options[:limit], scope)).
- offset(construct_limit(options[:offset], scope)).
- from(options[:from]).
- select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
+ relation = relation.joins(options[:joins]).
+ where(options[:conditions]).
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ limit(options[:limit]).
+ offset(options[:offset]).
+ from(options[:from])
+
+ relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods
+ relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order]))
relation.to_sql
end
def using_limitable_reflections?(reflections)
- reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
+ reflections.collect(&:collection?).length.zero?
end
def column_aliases(join_dependency)
@@ -1841,7 +1847,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+ if reflection && reflection.collection?
records.each { |record| record.send(reflection.name).target.uniq! }
end
when Array
@@ -1851,12 +1857,11 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
- is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
parent_records = []
records.each do |record|
if descendant = record.send(reflection.name)
- if is_collection
+ if reflection.collection?
parent_records.concat descendant.target.uniq
else
parent_records << descendant
@@ -1954,7 +1959,7 @@ module ActiveRecord
class JoinBase # :nodoc:
attr_reader :active_record, :table_joins
- delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
def initialize(active_record, joins = nil)
@active_record = active_record
@@ -2029,140 +2034,108 @@ module ActiveRecord
def association_join
return @join if @join
- connection = reflection.active_record.connection
+
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
+
@join = case reflection.macro
- when :has_and_belongs_to_many
- ["%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_join_table_name),
- options[:foreign_key] || reflection.active_record.to_s.foreign_key,
- connection.quote_table_name(parent.aliased_table_name),
- reflection.active_record.primary_key],
- "%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_table_name),
- klass.primary_key,
- connection.quote_table_name(aliased_join_table_name),
- options[:association_foreign_key] || klass.to_s.foreign_key
- ]
- ]
- when :has_many, :has_one
- if reflection.options[:through]
- jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
- first_key = second_key = as_extra = nil
-
- if through_reflection.options[:as] # has_many :through against a polymorphic join
- jt_foreign_key = through_reflection.options[:as].to_s + '_id'
- jt_as_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
- klass.quote_value(parent.active_record.base_class.name)
- ]
+ when :has_and_belongs_to_many
+ join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
+ klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
+
+ [
+ join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
+ aliased_table[klass.primary_key].eq(join_table[klass_fk])
+ ]
+ when :has_many, :has_one
+ if reflection.options[:through]
+ join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
+ first_key = second_key = as_extra = nil
+
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
+ jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
+ else
+ jt_foreign_key = through_reflection.primary_key_name
+ end
+
+ case source_reflection.macro
+ when :has_many
+ if source_reflection.options[:as]
+ first_key = "#{source_reflection.options[:as]}_id"
+ second_key = options[:foreign_key] || primary_key
+ as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
else
- jt_foreign_key = through_reflection.primary_key_name
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
+ second_key = options[:foreign_key] || primary_key
end
- case source_reflection.macro
- when :has_many
- if source_reflection.options[:as]
- first_key = "#{source_reflection.options[:as]}_id"
- second_key = options[:foreign_key] || primary_key
- as_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_table_name),
- connection.quote_column_name("#{source_reflection.options[:as]}_type"),
- klass.quote_value(source_reflection.active_record.base_class.name)
- ]
- else
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- second_key = options[:foreign_key] || primary_key
- end
-
- unless through_reflection.klass.descends_from_active_record?
- jt_sti_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(through_reflection.active_record.inheritance_column),
- through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
- end
- when :belongs_to
- first_key = primary_key
- if reflection.options[:source_type]
- second_key = source_reflection.association_foreign_key
- jt_source_extra = " AND %s.%s = %s" % [
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
- klass.quote_value(reflection.options[:source_type])
- ]
- else
- second_key = source_reflection.primary_key_name
- end
+ unless through_reflection.klass.descends_from_active_record?
+ jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
+ end
+ when :belongs_to
+ first_key = primary_key
+ if reflection.options[:source_type]
+ second_key = source_reflection.association_foreign_key
+ jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
+ else
+ second_key = source_reflection.primary_key_name
end
-
- ["(%s.%s = %s.%s%s%s%s) " % [
- connection.quote_table_name(parent.aliased_table_name),
- connection.quote_column_name(parent.primary_key),
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(jt_foreign_key),
- jt_as_extra, jt_source_extra, jt_sti_extra],
- "(%s.%s = %s.%s%s) " % [
- connection.quote_table_name(aliased_table_name),
- connection.quote_column_name(first_key),
- connection.quote_table_name(aliased_join_table_name),
- connection.quote_column_name(second_key),
- as_extra]
- ]
-
- elsif reflection.options[:as]
- "%s.%s = %s.%s AND %s.%s = %s" % [
- connection.quote_table_name(aliased_table_name),
- "#{reflection.options[:as]}_id",
- connection.quote_table_name(parent.aliased_table_name),
- parent.primary_key,
- connection.quote_table_name(aliased_table_name),
- "#{reflection.options[:as]}_type",
- klass.quote_value(parent.active_record.base_class.name)
- ]
- else
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
- "%s.%s = %s.%s " % [
- aliased_table_name,
- foreign_key,
- parent.aliased_table_name,
- reflection.options[:primary_key] || parent.primary_key
- ]
end
- when :belongs_to
- "%s.%s = %s.%s " % [
- connection.quote_table_name(aliased_table_name),
- reflection.klass.primary_key,
- connection.quote_table_name(parent.aliased_table_name),
- options[:foreign_key] || reflection.primary_key_name
+
+ [
+ [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
+ aliased_table[first_key].eq(join_table[second_key])
]
+ elsif reflection.options[:as]
+ id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
+ type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
+ [id_rel, type_rel]
+ else
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
+ [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
+ end
+ when :belongs_to
+ [aliased_table[reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
+ end
+
+ unless klass.descends_from_active_record?
+ sti_column = aliased_table[klass.inheritance_column]
+ sti_condition = sti_column.eq(klass.sti_name)
+ klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+
+ @join << sti_condition
end
- @join << %(AND %s) % [
- klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
- @join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
+ if ref && ref.options[:conditions]
+ @join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
+ end
end
@join
end
def relation
+ aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
+
if reflection.macro == :has_and_belongs_to_many
- [Arel::Table.new(table_alias_for(options[:join_table], aliased_join_table_name)), Arel::Table.new(table_name_and_alias)]
+ [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
elsif reflection.options[:through]
- [Arel::Table.new(table_alias_for(through_reflection.klass.table_name, aliased_join_table_name)), Arel::Table.new(table_name_and_alias)]
+ [Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
else
- Arel::Table.new(table_name_and_alias)
+ aliased
end
end
def join_relation(joining_relation, join = nil)
if (relations = relation).is_a?(Array)
- joining_relation.
- joins(relations.first, Arel::OuterJoin).on(association_join.first).
- joins(relations.last, Arel::OuterJoin).on(association_join.last)
+ joining_relation.joins(Relation::JoinOperation.new(relations.first, Arel::OuterJoin, association_join.first)).
+ joins(Relation::JoinOperation.new(relations.last, Arel::OuterJoin, association_join.last))
else
- joining_relation.joins(relations, Arel::OuterJoin).on(association_join)
+ joining_relation.joins(Relation::JoinOperation.new(relations, Arel::OuterJoin, association_join))
end
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 358db6df1d..64dd5cf629 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -58,11 +58,14 @@ module ActiveRecord
find_scope = construct_scope[:find].slice(:conditions, :order)
with_scope(:find => find_scope) do
- relation = @reflection.klass.send(:construct_finder_arel, options)
+ relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
case args.first
- when :first, :last, :all
+ when :first, :last
relation.send(args.first)
+ when :all
+ records = relation.all
+ @reflection.options[:uniq] ? uniq(records) : records
else
relation.find(*args)
end
@@ -402,7 +405,7 @@ module ActiveRecord
end
elsif @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args)
- else
+ else
with_scope(construct_scope) do
if block_given?
@reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
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 214ce5959a..387b85aacd 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -51,8 +51,6 @@ module ActiveRecord
end
def construct_find_options!(options)
- options[:select] = construct_select(options[:select])
- options[:from] ||= construct_from
options[:joins] = construct_joins(options[:joins])
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 1924156e2a..6f0f698f1e 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -6,8 +6,7 @@ module ActiveRecord
def construct_scope
{ :create => construct_owner_attributes(@reflection),
- :find => { :from => construct_from,
- :conditions => construct_conditions,
+ :find => { :conditions => construct_conditions,
:joins => construct_joins,
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
:select => construct_select,
@@ -145,7 +144,7 @@ module ActiveRecord
end
def build_sti_condition
- @reflection.through_reflection.klass.send(:type_condition)
+ @reflection.through_reflection.klass.send(:type_condition).to_sql
end
alias_method :sql_conditions, :conditions
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 98ab64537e..e178cb4ef2 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -158,47 +158,39 @@ module ActiveRecord
#
# For performance reasons, we don't check whether to validate at runtime,
# but instead only define the method and callback when needed. However,
- # this can change, for instance, when using nested attributes. Since we
- # don't want the callbacks to get defined multiple times, there are
- # guards that check if the save or validation methods have already been
- # defined before actually defining them.
+ # this can change, for instance, when using nested attributes, which is
+ # called _after_ the association has been defined. Since we don't want
+ # the callbacks to get defined multiple times, there are guards that
+ # check if the save or validation methods have already been defined
+ # before actually defining them.
def add_autosave_association_callbacks(reflection)
- save_method = "autosave_associated_records_for_#{reflection.name}"
- validation_method = "validate_associated_records_for_#{reflection.name}"
- force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
+ collection = reflection.collection?
- case reflection.macro
- when :has_many, :has_and_belongs_to_many
- unless method_defined?(save_method)
+ unless method_defined?(save_method)
+ if collection
before_save :before_save_collection_association
define_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
- end
-
- if !method_defined?(validation_method) &&
- (force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false))
- define_method(validation_method) { validate_collection_association(reflection) }
- validate validation_method
- end
- else
- unless method_defined?(save_method)
- case reflection.macro
- when :has_one
+ else
+ if reflection.macro == :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
- when :belongs_to
+ else
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
end
+ end
- if !method_defined?(validation_method) && force_validation
- define_method(validation_method) { validate_single_association(reflection) }
- validate validation_method
- end
+ if reflection.validate? && !method_defined?(validation_method)
+ method = (collection ? :validate_collection_association : :validate_single_association)
+ define_method(validation_method) { send(method, reflection) }
+ validate validation_method
end
end
end
@@ -232,10 +224,10 @@ module ActiveRecord
def associated_records_to_validate_or_save(association, new_record, autosave)
if new_record
association
- elsif association.loaded?
- autosave ? association : association.find_all { |record| record.new_record? }
+ elsif autosave
+ association.target.find_all { |record| record.new_record? || record.changed? || record.marked_for_destruction? }
else
- autosave ? association.target : association.target.find_all { |record| record.new_record? }
+ association.target.find_all { |record| record.new_record? }
end
end
@@ -268,7 +260,8 @@ module ActiveRecord
if reflection.options[:autosave]
association.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
- errors[attribute] << message if errors[attribute].empty?
+ errors[attribute] << message
+ errors[attribute].uniq!
end
else
errors.add(reflection.name)
@@ -304,13 +297,15 @@ module ActiveRecord
association.destroy(record)
elsif autosave != false && (@new_record_before_save || record.new_record?)
if autosave
- association.send(:insert_record, record, false, false)
+ saved = association.send(:insert_record, record, false, false)
else
association.send(:insert_record, record)
end
elsif autosave
- record.save(false)
+ saved = record.save(false)
end
+
+ raise ActiveRecord::Rollback if saved == false
end
end
@@ -337,7 +332,9 @@ module ActiveRecord
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
association[reflection.primary_key_name] = key
- association.save(!autosave)
+ saved = association.save(!autosave)
+ raise ActiveRecord::Rollback if !saved && autosave
+ saved
end
end
end
@@ -358,7 +355,7 @@ module ActiveRecord
if autosave && association.marked_for_destruction?
association.destroy
elsif autosave != false
- association.save(!autosave) if association.new_record? || autosave
+ saved = association.save(!autosave) if association.new_record? || autosave
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
@@ -368,6 +365,8 @@ module ActiveRecord
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
+
+ saved if autosave
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ec7725d256..4ee9887186 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -518,14 +518,6 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
- # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
- # may complicate matters if you use software like syslog. This is true, by default.
- cattr_accessor :colorize_logging, :instance_writer => false
- @@colorize_logging = true
-
- ##
- # :singleton-method:
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
# This is set to :local by default.
cattr_accessor :default_timezone, :instance_writer => false
@@ -550,13 +542,20 @@ module ActiveRecord #:nodoc:
# Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class
- self.store_full_sti_class = false
+ self.store_full_sti_class = true
# Stores the default scope for the class
class_inheritable_accessor :default_scoping, :instance_writer => false
self.default_scoping = []
class << self # Class methods
+ def colorize_logging(*args)
+ ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
+ "config.active_record.colorize_logging are deprecated. Please use " <<
+ "Rails::Subscriber.colorize_logging or config.colorize_logging instead", caller
+ end
+ alias :colorize_logging= :colorize_logging
+
# Find operates with four different retrieval approaches:
#
# * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
@@ -643,9 +642,8 @@ module ActiveRecord #:nodoc:
# end
def find(*args)
options = args.extract_options!
- set_readonly_option!(options)
- relation = construct_finder_arel(options)
+ relation = construct_finder_arel(options, current_scoped_methods)
case args.first
when :first, :last, :all
@@ -816,7 +814,7 @@ module ActiveRecord #:nodoc:
# # Delete multiple rows
# Todo.delete([2,3,4])
def delete(id_or_array)
- active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array)
+ scoped.delete(id_or_array)
end
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
@@ -871,20 +869,18 @@ module ActiveRecord #:nodoc:
# # Update all books that match our conditions, but limit it to 5 ordered by date
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
- scope = scope(:find)
-
- relation = active_relation
+ relation = unscoped
- if conditions = construct_conditions(conditions, scope)
- relation = relation.where(Arel::SqlLiteral.new(conditions))
- end
+ relation = relation.where(conditions) if conditions
+ relation = relation.limit(options[:limit]) if options[:limit].present?
+ relation = relation.order(options[:order]) if options[:order].present?
- relation = if options.has_key?(:limit) || (scope && scope[:limit])
+ if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present?
# Only take order from scope if limit is also provided by scope, this
# is useful for updating a has_many association with a limit.
- relation.order(construct_order(options[:order], scope)).limit(construct_limit(options[:limit], scope))
+ relation = current_scoped_methods.merge(relation) if current_scoped_methods
else
- relation.order(options[:order])
+ relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods
end
relation.update(sanitize_sql_for_assignment(updates))
@@ -938,7 +934,7 @@ module ActiveRecord #:nodoc:
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
- active_relation.where(construct_conditions(conditions, scope(:find))).delete_all
+ where(conditions).delete_all
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1392,7 +1388,7 @@ module ActiveRecord #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @active_relation = @active_relation_engine = nil
+ @arel_engine = @unscoped = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -1505,20 +1501,21 @@ module ActiveRecord #:nodoc:
"(#{segments.join(') AND (')})" unless segments.empty?
end
- def active_relation
- @active_relation ||= Relation.new(self, active_relation_table)
+ def unscoped
+ @unscoped ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
end
- def active_relation_table(table_name_alias = nil)
- Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine)
+ def arel_table
+ @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
end
- def active_relation_engine
- @active_relation_engine ||= begin
+ def arel_engine
+ @arel_engine ||= begin
if self == ActiveRecord::Base
Arel::Table.engine
else
- connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
end
end
end
@@ -1565,111 +1562,36 @@ module ActiveRecord #:nodoc:
end
end
- def default_select(qualified)
- if qualified
- quoted_table_name + '.*'
- else
- '*'
- end
- end
-
- def construct_finder_arel(options = {}, scope = scope(:find))
- validate_find_options(options)
-
- relation = active_relation.
- joins(construct_join(options[:joins], scope)).
- where(construct_conditions(options[:conditions], scope)).
- select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))).
- group(options[:group] || (scope && scope[:group])).
- having(options[:having] || (scope && scope[:having])).
- order(construct_order(options[:order], scope)).
- limit(construct_limit(options[:limit], scope)).
- offset(construct_offset(options[:offset], scope)).
- from(options[:from]).
- includes( merge_includes(scope && scope[:include], options[:include]))
-
- lock = (scope && scope[:lock]) || options[:lock]
- relation = relation.lock if lock.present?
-
- relation = relation.readonly if options[:readonly]
-
+ def construct_finder_arel(options = {}, scope = nil)
+ relation = unscoped.apply_finder_options(options)
+ relation = scope.merge(relation) if scope
relation
end
- def construct_join(joins, scope)
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
- case merged_joins
+ def construct_join(joins)
+ case joins
when Symbol, Hash, Array
- if array_of_strings?(merged_joins)
- merged_joins.join(' ') + " "
+ if array_of_strings?(joins)
+ joins.join(' ') + " "
else
- build_association_joins(merged_joins)
+ build_association_joins(joins)
end
when String
- " #{merged_joins} "
+ " #{joins} "
else
""
end
end
- def construct_order(order, scope)
- orders = []
-
- scoped_order = scope[:order] if scope
- if order
- orders << order
- orders << scoped_order if scoped_order && scoped_order != order
- elsif scoped_order
- orders << scoped_order
- end
-
- orders.reject {|o| o.blank?}
- end
-
- def construct_limit(limit, scope)
- limit ||= scope[:limit] if scope
- limit
- end
-
- def construct_offset(offset, scope)
- offset ||= scope[:offset] if scope
- offset
- end
-
- def construct_conditions(conditions, scope)
- conditions = [conditions]
- conditions << scope[:conditions] if scope
- conditions << type_condition if finder_needs_type_condition?
- merge_conditions(*conditions)
- end
-
- # Merges includes so that the result is a valid +include+
- def merge_includes(first, second)
- (Array.wrap(first) + Array.wrap(second)).uniq
- end
-
- def merge_joins(*joins)
- if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
- joins = joins.collect do |join|
- join = [join] if join.is_a?(String)
- join = build_association_joins(join) unless array_of_strings?(join)
- join
- end
- joins.flatten.map{|j| j.strip}.uniq
- else
- joins.collect{|j| Array.wrap(j)}.flatten.uniq
- end
- end
-
def build_association_joins(joins)
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil)
- relation = active_relation.relation
+ relation = unscoped.table
join_dependency.join_associations.map { |association|
if (association_relation = association.relation).is_a?(Array)
- [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation),
- Arel::InnerJoin.new(relation, association_relation.last, association.association_join.last).joins(relation)].join()
+ [Arel::InnerJoin.new(relation, association_relation.first, *association.association_join.first).joins(relation),
+ Arel::InnerJoin.new(relation, association_relation.last, *association.association_join.last).joins(relation)].join()
else
- Arel::InnerJoin.new(relation, association_relation, association.association_join).joins(relation)
+ Arel::InnerJoin.new(relation, association_relation, *association.association_join).joins(relation)
end
}.join(" ")
end
@@ -1678,14 +1600,12 @@ module ActiveRecord #:nodoc:
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
end
- def type_condition(table_alias = nil)
- table = Arel::Table.new(table_name, :engine => active_relation_engine, :as => table_alias)
-
- sti_column = table[inheritance_column]
+ def type_condition
+ sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
- condition.to_sql
+ condition
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1714,7 +1634,7 @@ module ActiveRecord #:nodoc:
super unless all_attributes_exists?(attribute_names)
if match.finder?
options = arguments.extract_options!
- relation = options.any? ? construct_finder_arel(options) : scoped
+ relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
relation.send :find_by_attributes, match, attribute_names, *arguments
elsif match.instantiator?
scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
@@ -1804,10 +1724,10 @@ module ActiveRecord #:nodoc:
# class Article < ActiveRecord::Base
# def self.find_with_scope
# with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
- # with_scope(:find => { :limit => 10 })
+ # with_scope(:find => { :limit => 10 }) do
# find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
# end
- # with_scope(:find => { :conditions => "author_id = 3" })
+ # with_scope(:find => { :conditions => "author_id = 3" }) do
# find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
# end
# end
@@ -1831,52 +1751,43 @@ module ActiveRecord #:nodoc:
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
- # Dup first and second level of hash (method and params).
- method_scoping = method_scoping.inject({}) do |hash, (method, params)|
- hash[method] = (params == true) ? params : params.dup
- hash
- end
+ if method_scoping.is_a?(Hash)
+ # Dup first and second level of hash (method and params).
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
+ hash[method] = (params == true) ? params : params.dup
+ hash
+ end
+
+ method_scoping.assert_valid_keys([ :find, :create ])
+ relation = construct_finder_arel(method_scoping[:find] || {})
+
+ if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
+ scope_for_create = case action
+ when :merge
+ current_scoped_methods.create_with_value.merge(method_scoping[:create])
+ when :reverse_merge
+ method_scoping[:create].merge(current_scoped_methods.create_with_value)
+ else
+ method_scoping[:create]
+ end
- method_scoping.assert_valid_keys([ :find, :create ])
+ relation = relation.create_with(scope_for_create)
+ else
+ scope_for_create = method_scoping[:create]
+ scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
+ relation = relation.create_with(scope_for_create) if scope_for_create
+ end
- if f = method_scoping[:find]
- f.assert_valid_keys(VALID_FIND_OPTIONS)
- set_readonly_option! f
+ method_scoping = relation
end
- # Merge scopings
- if [:merge, :reverse_merge].include?(action) && current_scoped_methods
- method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
- case hash[method]
- when Hash
- if method == :find
- (hash[method].keys + params.keys).uniq.each do |key|
- merge = hash[method][key] && params[key] # merge if both scopes have the same key
- if key == :conditions && merge
- if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
- hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
- else
- hash[method][key] = merge_conditions(params[key], hash[method][key])
- end
- elsif key == :include && merge
- hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
- elsif key == :joins && merge
- hash[method][key] = merge_joins(params[key], hash[method][key])
- else
- hash[method][key] = hash[method][key] || params[key]
- end
- end
- else
- if action == :reverse_merge
- hash[method] = hash[method].merge(params)
- else
- hash[method] = params.merge(hash[method])
- end
- end
- else
- hash[method] = params
- end
- hash
+ if current_scoped_methods
+ case action
+ when :merge
+ method_scoping = current_scoped_methods.merge(method_scoping)
+ when :reverse_merge
+ method_scoping = current_scoped_methods.except(:where).merge(method_scoping)
+ method_scoping = method_scoping.merge(current_scoped_methods.only(:where))
end
end
@@ -1905,21 +1816,7 @@ module ActiveRecord #:nodoc:
# default_scope :order => 'last_name, first_name'
# end
def default_scope(options = {})
- self.default_scoping << { :find => options, :create => options[:conditions].is_a?(Hash) ? options[:conditions] : {} }
- end
-
- # Test whether the given method and optional key are scoped.
- def scoped?(method, key = nil) #:nodoc:
- if current_scoped_methods && (scope = current_scoped_methods[method])
- !key || !scope[key].nil?
- end
- end
-
- # Retrieve the scope for the given method and optional key.
- def scope(method, key = nil) #:nodoc:
- if current_scoped_methods && (scope = current_scoped_methods[method])
- key ? scope[key] : scope
- end
+ self.default_scoping << construct_finder_arel(options)
end
def scoped_methods #:nodoc:
@@ -2039,8 +1936,8 @@ module ActiveRecord #:nodoc:
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
- table = Arel::Table.new(default_table_name, active_relation_engine)
- builder = PredicateBuilder.new(active_relation_engine)
+ table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
+ builder = PredicateBuilder.new(arel_engine)
builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -2123,25 +2020,6 @@ module ActiveRecord #:nodoc:
end
end
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
- :order, :select, :readonly, :group, :having, :from, :lock ]
-
- def validate_find_options(options) #:nodoc:
- options.assert_valid_keys(VALID_FIND_OPTIONS)
- end
-
- def set_readonly_option!(options) #:nodoc:
- # Inherit :readonly from finder scope if set. Otherwise,
- # if :joins is not blank then :readonly defaults to true.
- unless options.has_key?(:readonly)
- if scoped_readonly = scope(:find, :readonly)
- options[:readonly] = scoped_readonly
- elsif !options[:joins].blank? && !options[:select]
- options[:readonly] = true
- end
- end
- end
-
def encode_quoted_value(value) #:nodoc:
quoted_value = connection.quote(value)
quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
@@ -2160,7 +2038,12 @@ module ActiveRecord #:nodoc:
@new_record = true
ensure_proper_type
self.attributes = attributes unless attributes.nil?
- self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
+
+ if scope = self.class.send(:current_scoped_methods)
+ create_with = scope.scope_for_create
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
+ end
+
result = yield self if block_given?
_run_initialize_callbacks
result
@@ -2186,7 +2069,11 @@ module ActiveRecord #:nodoc:
@attributes_cache = {}
@new_record = true
ensure_proper_type
- self.class.send(:scope, :create).each { |att, value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
+
+ if scope = self.class.send(:current_scoped_methods)
+ create_with = scope.scope_for_create
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
+ end
end
# Returns a String, which Action Pack uses for constructing an URL to this
@@ -2306,7 +2193,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
end
@destroyed = true
@@ -2593,7 +2480,7 @@ module ActiveRecord #:nodoc:
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
- self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2606,9 +2493,9 @@ module ActiveRecord #:nodoc:
attributes_values = arel_attributes_values
new_id = if attributes_values.empty?
- self.class.active_relation.insert connection.empty_insert_statement_value
+ self.class.unscoped.insert connection.empty_insert_statement_value
else
- self.class.active_relation.insert attributes_values
+ self.class.unscoped.insert attributes_values
end
self.id ||= new_id
@@ -2703,7 +2590,7 @@ module ActiveRecord #:nodoc:
if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
value = value.to_yaml
end
- attrs[self.class.active_relation[name]] = value
+ attrs[self.class.arel_table[name]] = value
end
end
end
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 20d287faeb..e4b3caab4e 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -46,19 +46,19 @@ module ActiveRecord
def count(*args)
case args.size
when 0
- construct_calculation_arel.count
+ construct_calculation_arel({}, current_scoped_methods).count
when 1
if args[0].is_a?(Hash)
options = args[0]
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options).count(options[:select], :distinct => distinct)
+ construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct)
else
- construct_calculation_arel.count(args[0])
+ construct_calculation_arel({}, current_scoped_methods).count(args[0])
end
when 2
column_name, options = args
distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false
- construct_calculation_arel(options).count(column_name, :distinct => distinct)
+ construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct)
else
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
end
@@ -141,7 +141,7 @@ module ActiveRecord
# Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct))
+ construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct))
rescue ThrowResult
0
end
@@ -151,49 +151,58 @@ module ActiveRecord
options.assert_valid_keys(CALCULATIONS_OPTIONS)
end
- def construct_calculation_arel(options = {})
+ def construct_calculation_arel(options = {}, merge_with_relation = nil)
validate_calculation_options(options)
options = options.except(:distinct)
- scope = scope(:find)
- includes = merge_includes(scope ? scope[:include] : [], options[:include])
+ merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : []
+ includes = (merge_with_includes + Array.wrap(options[:include])).uniq
if includes.any?
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope))
- construct_calculation_arel_with_included_associations(options, join_dependency)
+ merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : []
+ joins = (merge_with_joins + Array.wrap(options[:joins])).uniq
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins))
+ construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation)
else
- active_relation.
- joins(construct_join(options[:joins], scope)).
- from((scope && scope[:from]) || options[:from]).
- where(construct_conditions(options[:conditions], scope)).
- order(options[:order]).
- limit(options[:limit]).
- offset(options[:offset]).
- group(options[:group]).
- having(options[:having]).
- select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins])))
+ relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having))
+
+ if merge_with_relation
+ relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation)
+ end
+
+ from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present?
+ from = options[:from] if from.blank? && options[:from].present?
+ relation = relation.from(from)
+
+ select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil)
+ relation = relation.select(select)
+
+ relation
end
end
- def construct_calculation_arel_with_included_associations(options, join_dependency)
- scope = scope(:find)
-
- relation = active_relation
+ def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil)
+ relation = unscoped
for association in join_dependency.join_associations
relation = association.join_relation(relation)
end
- relation = relation.joins(construct_join(options[:joins], scope)).
- select(column_aliases(join_dependency)).
- group(options[:group]).
- having(options[:having]).
- order(options[:order]).
- where(construct_conditions(options[:conditions], scope)).
- from((scope && scope[:from]) || options[:from])
+ if merge_with_relation
+ relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq
+ relation.where_values = merge_with_relation.where_values
+
+ merge_limit = merge_with_relation.taken
+ end
+
+ relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)).
+ select(column_aliases(join_dependency))
+
+ if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit])
+ relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency))
+ end
- relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
- relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections)
+ relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections)
relation
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e2a8f03c8f..aecde5848c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -9,7 +9,6 @@ module ActiveRecord
# * (-) <tt>valid</tt>
# * (1) <tt>before_validation</tt>
# * (-) <tt>validate</tt>
- # * (-) <tt>validate_on_create</tt>
# * (2) <tt>after_validation</tt>
# * (3) <tt>before_save</tt>
# * (4) <tt>before_create</tt>
@@ -223,9 +222,10 @@ module ActiveRecord
extend ActiveModel::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
- define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
@@ -236,6 +236,24 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
+
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array(options[:if])
+ options[:if] << "@_on_validate == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
end
def create_or_update_with_callbacks #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 377f2a44c5..bf8c546d2e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# * +wait_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
class ConnectionPool
- attr_reader :spec
+ attr_reader :spec, :connections
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index bbc290f721..2f36bec764 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -51,8 +51,8 @@ module ActiveRecord
def self.establish_connection(spec = nil)
case spec
when nil
- raise AdapterNotSpecified unless defined? RAILS_ENV
- establish_connection(RAILS_ENV)
+ raise AdapterNotSpecified unless defined?(Rails.env)
+ establish_connection(Rails.env)
when ConnectionSpecification
@@connection_handler.establish_connection(name, spec)
when Symbol, String
@@ -88,26 +88,6 @@ module ActiveRecord
end
class << self
- # Deprecated and no longer has any effect.
- def allow_concurrency
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency has been deprecated and no longer has any effect. Please remove all references to allow_concurrency.")
- end
-
- # Deprecated and no longer has any effect.
- def allow_concurrency=(flag)
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency= has been deprecated and no longer has any effect. Please remove all references to allow_concurrency=.")
- end
-
- # Deprecated and no longer has any effect.
- def verification_timeout
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout has been deprecated and no longer has any effect. Please remove all references to verification_timeout.")
- end
-
- # Deprecated and no longer has any effect.
- def verification_timeout=(flag)
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.verification_timeout= has been deprecated and no longer has any effect. Please remove all references to verification_timeout=.")
- end
-
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work unrelated
# to any of the specific Active Records.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 00c71090f3..020acbbe5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -75,7 +75,8 @@ module ActiveRecord
def cache_sql(sql)
result =
if @query_cache.has_key?(sql)
- log_info(sql, "CACHE", 0.0)
+ ActiveSupport::Notifications.instrument("active_record.sql",
+ :sql => sql, :name => "CACHE", :connection_id => self.object_id)
@query_cache[sql]
else
@query_cache[sql] = yield
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 e731bc84f0..86ba7d72c3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -344,12 +344,12 @@ module ActiveRecord
end
end
- def assume_migrated_upto_version(version)
+ def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
- versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
+ versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
filename.split('/').last.split('_').first.to_i
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d09aa3c4d2..7e80347f75 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -39,7 +39,6 @@ module ActiveRecord
def initialize(connection, logger = nil) #:nodoc:
@connection, @logger = connection, logger
@runtime = 0
- @last_verification = 0
@query_cache_enabled = false
end
@@ -191,27 +190,19 @@ module ActiveRecord
"active_record_#{open_transactions}"
end
- def log_info(sql, name, ms)
- if @logger && @logger.debug?
- name = '%s (%.1fms)' % [name || 'SQL', ms]
- @logger.debug(format_log_entry(name, sql.squeeze(' ')))
- end
- end
-
protected
+
def log(sql, name)
+ name ||= "SQL"
result = nil
- ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do
+ ActiveSupport::Notifications.instrument("active_record.sql",
+ :sql => sql, :name => name, :connection_id => self.object_id) do
@runtime += Benchmark.ms { result = yield }
end
result
rescue Exception => e
- # Log message and raise exception.
- # Set last_verification to 0, so that connection gets verified
- # upon reentering the request loop
- @last_verification = 0
message = "#{e.class.name}: #{e.message}: #{sql}"
- log_info(message, name, 0)
+ @logger.debug message if @logger
raise translate_exception(e, message)
end
@@ -220,23 +211,6 @@ module ActiveRecord
ActiveRecord::StatementInvalid.new(message)
end
- def format_log_entry(message, dump = nil)
- if ActiveRecord::Base.colorize_logging
- if @@row_even
- @@row_even = false
- message_color, dump_color = "4;36;1", "0;1"
- else
- @@row_even = true
- message_color, dump_color = "4;35;1", "0"
- end
-
- log_entry = " \e[#{message_color}m#{message}\e[0m "
- log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
- log_entry
- else
- "%s %s" % [message, dump]
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index fa28bc64df..8c0bf6396a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -579,6 +579,8 @@ module ActiveRecord
protected
def translate_exception(exception, message)
+ return super unless exception.respond_to?(:errno)
+
case exception.errno
when 1062
RecordNotUnique.new(message, exception)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 78b897add6..0a52f3a6a2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Allow database path relative to Rails.root, but only if
# the database path is not the special path that tells
# Sqlite to build a database only in memory.
- if Object.const_defined?(:Rails) && ':memory:' != config[:database]
+ if defined?(Rails.root) && ':memory:' != config[:database]
config[:database] = File.expand_path(config[:database], Rails.root)
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index c94b31a856..79f70f07cd 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -3,7 +3,6 @@ require 'yaml'
require 'csv'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/test_case'
require 'active_support/core_ext/logger'
if RUBY_VERSION < '1.9'
@@ -434,7 +433,7 @@ end
# Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
- MAX_ID = 2 ** 31 - 1
+ MAX_ID = 2 ** 30 - 1
DEFAULT_FILTER_RE = /\.ya?ml$/
@@all_cached_fixtures = {}
@@ -822,8 +821,8 @@ module ActiveRecord
superclass_delegating_accessor :pre_loaded_fixtures
self.fixture_table_names = []
- self.use_transactional_fixtures = false
- self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = true
+ self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.fixture_class_names = {}
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index e33d389f8c..4115cc8e17 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -1,33 +1,9 @@
en:
- activerecord:
- errors:
- # model.errors.full_messages format.
- format: "{{attribute}} {{message}}"
-
- # The values :model, :attribute and :value are always available for interpolation
- # The value :count is available when applicable. Can be used for pluralization.
- messages:
- inclusion: "is not included in the list"
- exclusion: "is reserved"
- invalid: "is invalid"
- confirmation: "doesn't match confirmation"
- accepted: "must be accepted"
- empty: "can't be empty"
- blank: "can't be blank"
- too_long: "is too long (maximum is {{count}} characters)"
- too_short: "is too short (minimum is {{count}} characters)"
- wrong_length: "is the wrong length (should be {{count}} characters)"
- taken: "has already been taken"
- not_a_number: "is not a number"
- greater_than: "must be greater than {{count}}"
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
- equal_to: "must be equal to {{count}}"
- less_than: "must be less than {{count}}"
- less_than_or_equal_to: "must be less than or equal to {{count}}"
- odd: "must be odd"
- even: "must be even"
- record_invalid: "Validation failed: {{errors}}"
- # Append your own errors here or at the model/attributes scope.
+ errors:
+ messages:
+ taken: "has already been taken"
+ record_invalid: "Validation failed: {{errors}}"
+ # Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@@ -42,7 +18,14 @@ en:
# Will define custom blank validation message for User model and
# custom blank validation message for login attribute of User model.
#models:
-
+
+ # Attributes names common to most models
+ #attributes:
+ #created_at: "Created at"
+ #updated_at: "Updated at"
+
+ # ActiveRecord models configuration
+ #activerecord:
# Translate model names. Used in Model.human_name().
#models:
# For example,
@@ -55,4 +38,3 @@ en:
# user:
# login: "Handle"
# will translate User attribute "login" as "Handle"
-
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index f9e538c586..9fcdabdb44 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -78,7 +78,7 @@ module ActiveRecord
attribute_names.uniq!
begin
- relation = self.class.active_relation
+ relation = self.class.unscoped
affected_rows = relation.where(
relation[self.class.primary_key].eq(quoted_id).and(
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c218c5bfd1..c059b2d18b 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -340,6 +340,10 @@ module ActiveRecord
self.verbose = save
end
+ def connection
+ ActiveRecord::Base.connection
+ end
+
def method_missing(method, *arguments, &block)
arg_list = arguments.map(&:inspect) * ', '
@@ -347,7 +351,7 @@ module ActiveRecord
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
end
- Base.connection.send(method, *arguments, &block)
+ connection.send(method, *arguments, &block)
end
end
end
@@ -404,6 +408,10 @@ module ActiveRecord
self.new(direction, migrations_path, target_version).run
end
+ def migrations_path
+ 'db/migrate'
+ end
+
def schema_migrations_table_name
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index f63b249241..90fd700437 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -26,10 +26,12 @@ module ActiveRecord
if options.present?
Scope.new(self, options, &block)
else
- unless scoped?(:find)
- finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn
+ current_scope = current_scoped_methods
+
+ unless current_scope
+ unscoped.spawn
else
- construct_finder_arel
+ construct_finder_arel({}, current_scoped_methods)
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index dbdeba6c24..d8fae495e7 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -188,6 +188,8 @@ module ActiveRecord
# the parent model is saved. This happens inside the transaction initiated
# by the parents save method. See ActiveRecord::AutosaveAssociation.
module ClassMethods
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
+
# Defines an attributes writer for the specified association(s). If you
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
# will need to add the attribute writer to the allowed list.
@@ -229,23 +231,14 @@ module ActiveRecord
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
- type = case reflection.macro
- when :has_one, :belongs_to
- :one_to_one
- when :has_many, :has_and_belongs_to_many
- :collection
- end
-
reflection.options[:autosave] = true
add_autosave_association_callbacks(reflection)
- self.nested_attributes_options[association_name.to_sym] = options
-
- if options[:reject_if] == :all_blank
- self.nested_attributes_options[association_name.to_sym][:reject_if] = proc { |attributes| attributes.all? {|k,v| v.blank?} }
- end
+ nested_attributes_options[association_name.to_sym] = options
+ type = (reflection.collection? ? :collection : :one_to_one)
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
@@ -271,21 +264,11 @@ module ActiveRecord
marked_for_destruction?
end
- # Deal with deprecated _delete.
- #
- def _delete #:nodoc:
- ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
- _destroy
- end
-
private
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
- #
- # TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
- # removed.
- UNASSIGNABLE_KEYS = %w( id _destroy _delete )
+ UNASSIGNABLE_KEYS = %w( id _destroy )
# Assigns the given attributes to the association.
#
@@ -298,13 +281,17 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
if check_existing_record && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
+
+ elsif attributes['id']
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
@@ -343,7 +330,7 @@ module ActiveRecord
# { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -366,6 +353,8 @@ module ActiveRecord
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ else
+ raise_nested_attributes_record_not_found(association_name, attributes['id'])
end
end
end
@@ -382,8 +371,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
- ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
+ ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
# Determines if a new record should be build by checking for
@@ -394,14 +382,17 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
- callback = self.nested_attributes_options[association_name][:reject_if]
-
- case callback
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
- callback.try(:call, attributes)
+ callback.call(attributes)
end
end
+
+ def raise_nested_attributes_record_not_found(association_name, record_id)
+ reflection = self.class.reflect_on_association(association_name)
+ raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 657ee738c0..bc06333f1c 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -14,6 +14,10 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
+ # TODO If we require the wrong file, the error never comes up.
+ require "active_record/railties/subscriber"
+ subscriber ActiveRecord::Railties::Subscriber.new
+
initializer "active_record.set_configs" do |app|
app.config.active_record.each do |k,v|
ActiveRecord::Base.send "#{k}=", v
@@ -52,6 +56,19 @@ module ActiveRecord
initializer "active_record.load_observers" do
ActiveRecord::Base.instantiate_observers
+
+ ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
+ ActiveRecord::Base.instantiate_observers
+ end
+ end
+
+ initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
+ unless app.config.cache_classes
+ ActionDispatch::Callbacks.after do
+ ActiveRecord::Base.reset_subclasses
+ ActiveRecord::Base.clear_reloadable_connections!
+ end
+ end
end
# TODO: ActiveRecord::Base.logger should delegate to its own config.logger
@@ -59,13 +76,16 @@ module ActiveRecord
ActiveRecord::Base.logger ||= ::Rails.logger
end
- initializer "active_record.notifications" do
- require 'active_support/notifications'
+ initializer "active_record.i18n_deprecation" do
+ require 'active_support/i18n'
- ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload|
- ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000)
+ begin
+ I18n.t(:"activerecord.errors", :raise => true)
+ warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " <<
+ "yml files, please use just \"errors\" instead."
+ rescue Exception => e
+ # No message then.
end
end
-
end
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 535e967ec3..aed1c59b00 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -5,6 +5,8 @@ module ActiveRecord
module ControllerRuntime
extend ActiveSupport::Concern
+ protected
+
attr_internal :db_runtime
def cleanup_view_runtime
@@ -19,11 +21,16 @@ module ActiveRecord
end
end
+ def append_info_to_payload(payload)
+ super
+ payload[:db_runtime] = db_runtime
+ end
+
module ClassMethods
- def log_process_action(controller)
- super
- db_runtime = controller.send :db_runtime
- logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime
+ def log_process_action(payload)
+ messages, db_runtime = super, payload[:db_runtime]
+ messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
+ messages
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a35a6c156b..b39e064e45 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -26,9 +26,9 @@ namespace :db do
end
end
- desc 'Create the database defined in config/database.yml for the current RAILS_ENV'
+ desc 'Create the database defined in config/database.yml for the current Rails.env'
task :create => :load_config do
- create_database(ActiveRecord::Base.configurations[RAILS_ENV])
+ create_database(ActiveRecord::Base.configurations[Rails.env])
end
def create_database(config)
@@ -46,7 +46,7 @@ namespace :db do
$stderr.puts "Couldn't create database for #{config.inspect}"
end
end
- return # Skip the else clause of begin/rescue
+ return # Skip the else clause of begin/rescue
else
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection
@@ -111,9 +111,9 @@ namespace :db do
end
end
- desc 'Drops the database for the current RAILS_ENV'
+ desc 'Drops the database for the current Rails.env'
task :drop => :load_config do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
begin
drop_database(config)
rescue Exception => e
@@ -188,7 +188,7 @@ namespace :db do
desc "Retrieves the charset for the current environment's database"
task :charset => :environment do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when 'mysql'
ActiveRecord::Base.establish_connection(config)
@@ -203,7 +203,7 @@ namespace :db do
desc "Retrieves the collation for the current environment's database"
task :collation => :environment do
- config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when 'mysql'
ActiveRecord::Base.establish_connection(config)
@@ -246,6 +246,7 @@ namespace :db do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => :environment do
require 'active_record/fixtures'
+
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
@@ -257,7 +258,7 @@ namespace :db do
desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :identify => :environment do
- require "active_record/fixtures"
+ require 'active_record/fixtures'
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -295,7 +296,7 @@ namespace :db do
if File.exists?(file)
load(file)
else
- abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]}
+ abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/boot.rb to limit the frameworks that will be loaded}
end
end
end
@@ -304,36 +305,36 @@ namespace :db do
desc "Dump the database structure to a SQL file"
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs[RAILS_ENV]["adapter"]
+ case abcs[Rails.env]["adapter"]
when "mysql", "oci", "oracle"
- ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
- File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ ActiveRecord::Base.establish_connection(abcs[Rails.env])
+ File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
- ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
- ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
- ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
- search_path = abcs[RAILS_ENV]["schema_search_path"]
+ ENV['PGHOST'] = abcs[Rails.env]["host"] if abcs[Rails.env]["host"]
+ ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]["port"]
+ ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"]
+ search_path = abcs[Rails.env]["schema_search_path"]
unless search_path.blank?
search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
end
- `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}`
+ `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}`
raise "Error dumping database" if $?.exitstatus == 1
when "sqlite", "sqlite3"
- dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"]
- `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql`
+ dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"]
+ `#{abcs[Rails.env]["adapter"]} #{dbfile} .schema > db/#{Rails.env}_structure.sql`
when "sqlserver"
- `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r`
- `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r`
+ `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r`
+ `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r`
when "firebird"
- set_firebird_env(abcs[RAILS_ENV])
- db_string = firebird_db_string(abcs[RAILS_ENV])
- sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql"
+ set_firebird_env(abcs[Rails.env])
+ db_string = firebird_db_string(abcs[Rails.env])
+ sh "isql -a #{db_string} > #{Rails.root}/db/#{Rails.env}_structure.sql"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
end
end
end
@@ -356,28 +357,28 @@ namespace :db do
when "mysql"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
+ IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
+ `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]}`
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql`
+ `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
when "sqlserver"
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+ `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
- IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
+ IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when "firebird"
set_firebird_env(abcs["test"])
db_string = firebird_db_string(abcs["test"])
- sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}"
+ sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
@@ -400,7 +401,7 @@ namespace :db do
when "sqlserver"
dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-')
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}`
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
+ `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/subscriber.rb
new file mode 100644
index 0000000000..7c2a10cf0f
--- /dev/null
+++ b/activerecord/lib/active_record/railties/subscriber.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def sql(event)
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
+ sql = event.payload[:sql].squeeze(' ')
+
+ if odd?
+ name = color(name, :cyan, true)
+ sql = color(sql, nil, true)
+ else
+ name = color(name, :magenta, true)
+ end
+
+ debug "#{name} #{sql}"
+ end
+
+ def odd?
+ @odd_or_even = !@odd_or_even
+ end
+
+ def logger
+ ActiveRecord::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index b751c9ad68..32b9a2aa87 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -252,10 +252,33 @@ module ActiveRecord
end
end
+ # Returns whether or not this association reflection is for a collection
+ # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # +has_and_belongs_to_many+, +false+ otherwise.
+ def collection?
+ if @collection.nil?
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ end
+ @collection
+ end
+
+ # Returns whether or not the association should be validated as part of
+ # the parent's validation.
+ #
+ # Unless you explicitely disable validation with
+ # <tt>:validate => false</tt>, it will take place when:
+ #
+ # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you use autosave; <tt>:autosave => true</tt>
+ # * the association is a +has_many+ association
+ def validate?
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
- class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name = class_name.singularize if collection?
class_name
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 6b9925d4e7..85bf878416 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,19 +1,19 @@
module ActiveRecord
class Relation
- include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods
+ JoinOperation = Struct.new(:relation, :join_class, :on)
+ ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
+ MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
+
+ include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods
delegate :length, :collect, :map, :each, :all?, :to => :to_a
- attr_reader :relation, :klass
- attr_writer :readonly, :table
- attr_accessor :preload_associations, :eager_load_associations, :includes_associations
+ attr_reader :table, :klass
- def initialize(klass, relation)
- @klass, @relation = klass, relation
- @preload_associations = []
- @eager_load_associations = []
- @includes_associations = []
- @loaded, @readonly = false
+ def initialize(klass, table)
+ @klass, @table = klass, table
+ (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
end
def new(*args, &block)
@@ -29,7 +29,7 @@ module ActiveRecord
end
def respond_to?(method, include_private = false)
- return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
+ return true if arel.respond_to?(method, include_private) || Array.method_defined?(method)
if match = DynamicFinderMatch.match(method)
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
@@ -43,33 +43,38 @@ module ActiveRecord
def to_a
return @records if loaded?
- find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
+ find_with_associations = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)
@records = if find_with_associations
begin
- @klass.send(:find_with_associations, {
- :select => @relation.send(:select_clauses).join(', '),
- :joins => @relation.joins(relation),
- :group => @relation.send(:group_clauses).join(', '),
+ options = {
+ :select => @select_values.any? ? @select_values.join(", ") : nil,
+ :joins => arel.joins(arel),
+ :group => @group_values.any? ? @group_values.join(", ") : nil,
:order => order_clause,
:conditions => where_clause,
- :limit => @relation.taken,
- :offset => @relation.skipped,
- :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
- },
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil))
+ :limit => arel.taken,
+ :offset => arel.skipped,
+ :from => (arel.send(:from_clauses) if arel.send(:sources).present?)
+ }
+
+ including = (@eager_load_values + @includes_values).uniq
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
+ @klass.send(:find_with_associations, options, join_dependency)
rescue ThrowResult
[]
end
else
- @klass.find_by_sql(@relation.to_sql)
+ @klass.find_by_sql(arel.to_sql)
end
- preload = @preload_associations
- preload += @includes_associations unless find_with_associations
+ preload = @preload_values
+ preload += @includes_values unless find_with_associations
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- @records.each { |record| record.readonly! } if @readonly
+ # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
+ @records.each { |record| record.readonly! } if readonly
@loaded = true
@records
@@ -97,7 +102,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @relation.send(:taken).present? ? to_a.many? : size > 1
+ arel.send(:taken).present? ? to_a.many? : size > 1
end
end
@@ -107,7 +112,7 @@ module ActiveRecord
end
def delete_all
- @relation.delete.tap { reset }
+ arel.delete.tap { reset }
end
def delete(id_or_array)
@@ -124,28 +129,33 @@ module ActiveRecord
end
def reset
- @first = @last = @create_scope = @to_sql = @order_clause = nil
+ @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = nil
@records = []
self
end
- def table
- @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine)
- end
-
def primary_key
@primary_key ||= table[@klass.primary_key]
end
def to_sql
- @to_sql ||= @relation.to_sql
+ @to_sql ||= arel.to_sql
+ end
+
+ def scope_for_create
+ @scope_for_create ||= begin
+ @create_with_value || wheres.inject({}) do |hash, where|
+ hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
+ hash
+ end
+ end
end
protected
def method_missing(method, *args, &block)
- if @relation.respond_to?(method)
- @relation.send(method, *args, &block)
+ if arel.respond_to?(method)
+ arel.send(method, *args, &block)
elsif Array.method_defined?(method)
to_a.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
@@ -163,26 +173,19 @@ module ActiveRecord
end
def with_create_scope
- @klass.send(:with_scope, :create => create_scope) { yield }
- end
-
- def create_scope
- @create_scope ||= wheres.inject({}) do |hash, where|
- hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
- hash
- end
+ @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
end
def where_clause(join_string = " AND ")
- @relation.send(:where_clauses).join(join_string)
+ arel.send(:where_clauses).join(join_string)
end
def order_clause
- @order_clause ||= @relation.send(:order_clauses).join(', ')
+ @order_clause ||= arel.send(:order_clauses).join(', ')
end
def references_eager_loaded_tables?
- joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
+ joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq
(tables_in_string(to_sql) - joined_tables).any?
end
diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb
index 5246c7bc5d..91de89e607 100644
--- a/activerecord/lib/active_record/relation/calculation_methods.rb
+++ b/activerecord/lib/active_record/relation/calculation_methods.rb
@@ -25,7 +25,7 @@ module ActiveRecord
operation = operation.to_s.downcase
if operation == "count"
- joins = @relation.joins(relation)
+ joins = arel.joins(arel)
if joins.present? && joins =~ /LEFT OUTER/i
distinct = true
column_name = @klass.primary_key if column_name == :all
@@ -40,7 +40,7 @@ module ActiveRecord
distinct = options[:distinct] || distinct
column_name = :all if column_name.blank? && operation == "count"
- if @relation.send(:groupings).any?
+ if @group_values.any?
return execute_grouped_calculation(operation, column_name)
else
return execute_simple_calculation(operation, column_name, distinct)
@@ -53,7 +53,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column = if @klass.column_names.include?(column_name.to_s)
- Arel::Attribute.new(@klass.active_relation, column_name)
+ Arel::Attribute.new(@klass.unscoped, column_name)
else
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
end
@@ -63,7 +63,7 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name) #:nodoc:
- group_attr = @relation.send(:groupings).first.value
+ group_attr = @group_values.first
association = @klass.reflect_on_association(group_attr.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_field = associated ? association.primary_key_name : group_attr
@@ -77,7 +77,7 @@ module ActiveRecord
select_statement = if operation == 'count' && column_name == :all
"COUNT(*) AS count_all"
else
- Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql
+ Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql
end
select_statement << ", #{group_field} AS #{group_alias}"
@@ -106,7 +106,6 @@ module ActiveRecord
column_name = :all
# Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true)
- # TODO : relation.projections only works when .select() was last in the chain. Fix it!
case args.size
when 0
select = get_projection_name_from_chained_relations
@@ -165,12 +164,8 @@ module ActiveRecord
column ? column.type_cast(value) : value
end
- def get_projection_name_from_chained_relations(relation = @relation)
- if relation.respond_to?(:projections) && relation.projections.present?
- relation.send(:select_clauses).join(', ')
- elsif relation.respond_to?(:relation)
- get_projection_name_from_chained_relations(relation.relation)
- end
+ def get_projection_name_from_chained_relations
+ @select_values.join(", ") if @select_values.present?
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c3e5f27838..3668b0997f 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -93,15 +93,15 @@ module ActiveRecord
result = where(primary_key.in(ids)).all
expected_size =
- if @relation.taken && ids.size > @relation.taken
- @relation.taken
+ if arel.taken && ids.size > arel.taken
+ arel.taken
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
- if @relation.skipped && (ids.size - @relation.skipped < expected_size)
- expected_size = ids.size - @relation.skipped
+ if arel.skipped && (ids.size - arel.skipped < expected_size)
+ expected_size = ids.size - arel.skipped
end
if result.size == expected_size
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 525a9cb365..a3ac58bc81 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,72 +1,61 @@
module ActiveRecord
module QueryMethods
-
- def preload(*associations)
- spawn.tap {|r| r.preload_associations += Array.wrap(associations) }
- end
-
- def includes(*associations)
- spawn.tap {|r| r.includes_associations += Array.wrap(associations) }
- end
-
- def eager_load(*associations)
- spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) }
- end
-
- def readonly(status = true)
- spawn.tap {|r| r.readonly = status }
- end
-
- def select(selects)
- if selects.present?
- relation = spawn(@relation.project(selects))
- relation.readonly = @relation.joins(relation).present? ? false : @readonly
- relation
- else
- spawn
+ extend ActiveSupport::Concern
+
+ included do
+ (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
+ attr_accessor :"#{query_method}_values"
+
+ class_eval <<-CEVAL
+ def #{query_method}(*args)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_values ||= []
+ value = Array.wrap(args.flatten).reject {|x| x.blank? }
+ new_relation.#{query_method}_values += value if value.present?
+ end
+ end
+ CEVAL
end
- end
-
- def from(from)
- from.present? ? spawn(@relation.from(from)) : spawn
- end
- def having(*args)
- return spawn if args.blank?
-
- if [String, Hash, Array].include?(args.first.class)
- havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
- else
- havings = args.first
+ [:where, :having].each do |query_method|
+ class_eval <<-CEVAL
+ def #{query_method}(*args)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_values ||= []
+ value = build_where(*args)
+ new_relation.#{query_method}_values += [*value] if value.present?
+ end
+ end
+ CEVAL
end
- spawn(@relation.having(havings))
- end
+ ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
+ attr_accessor :"#{query_method}_value"
- def group(groups)
- groups.present? ? spawn(@relation.group(groups)) : spawn
- end
-
- def order(orders)
- orders.present? ? spawn(@relation.order(orders)) : spawn
+ class_eval <<-CEVAL
+ def #{query_method}(value = true)
+ spawn.tap do |new_relation|
+ new_relation.#{query_method}_value = value
+ end
+ end
+ CEVAL
+ end
end
def lock(locks = true)
+ relation = spawn
case locks
- when String
- spawn(@relation.lock(locks))
- when TrueClass, NilClass
- spawn(@relation.lock)
+ when String, TrueClass, NilClass
+ spawn.tap {|new_relation| new_relation.lock_value = locks || true }
else
- spawn
+ spawn.tap {|new_relation| new_relation.lock_value = false }
end
end
def reverse_order
- relation = spawn
- relation.instance_variable_set(:@orders, nil)
+ order_clause = arel.send(:order_clauses).join(', ')
+ relation = except(:order)
- order_clause = @relation.send(:order_clauses).join(', ')
if order_clause.present?
relation.order(reverse_sql_order(order_clause))
else
@@ -74,41 +63,108 @@ module ActiveRecord
end
end
- def limit(limits)
- limits.present? ? spawn(@relation.take(limits)) : spawn
+ def arel
+ @arel ||= build_arel
end
- def offset(offsets)
- offsets.present? ? spawn(@relation.skip(offsets)) : spawn
- end
+ def build_arel
+ arel = table
- def on(join)
- spawn(@relation.on(join))
- end
+ joined_associations = []
+ association_joins = []
- def joins(join, join_type = nil)
- return spawn if join.blank?
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
- join_relation = case join
- when String
- @relation.join(join)
- when Hash, Array, Symbol
- if @klass.send(:array_of_strings?, join)
- @relation.join(join.join(' '))
+ # Build association joins first
+ joins.each do |join|
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !@klass.send(:array_of_strings?, join)
+ end
+
+ if association_joins.any?
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins.uniq, nil)
+ to_join = []
+
+ join_dependency.join_associations.each do |association|
+ if (association_relation = association.relation).is_a?(Array)
+ to_join << [association_relation.first, association.association_join.first]
+ to_join << [association_relation.last, association.association_join.last]
+ else
+ to_join << [association_relation, association.association_join]
+ end
+ end
+
+ to_join.each do |tj|
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] }
+ joined_associations << tj
+ arel = arel.join(tj[0]).on(*tj[1])
+ end
+ end
+ end
+
+ joins.each do |join|
+ next if join.blank?
+
+ @implicit_readonly = true
+
+ case join
+ when Relation::JoinOperation
+ arel = arel.join(join.relation, join.join_class).on(*join.on)
+ when Hash, Array, Symbol
+ if @klass.send(:array_of_strings?, join)
+ join_string = join.join(' ')
+ arel = arel.join(join_string)
+ end
else
- @relation.join(@klass.send(:build_association_joins, join))
+ arel = arel.join(join)
end
- else
- @relation.join(join, join_type)
end
- spawn(join_relation).tap { |r| r.readonly = true }
+ @where_values.uniq.each do |w|
+ arel = w.is_a?(String) ? arel.where(w) : arel.where(*w)
+ end
+
+ @having_values.uniq.each do |h|
+ arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
+ end
+
+ arel = arel.take(@limit_value) if @limit_value.present?
+ arel = arel.skip(@offset_value) if @offset_value.present?
+
+ @group_values.uniq.each do |g|
+ arel = arel.group(g) if g.present?
+ end
+
+ @order_values.uniq.each do |o|
+ arel = arel.order(o) if o.present?
+ end
+
+ selects = @select_values.uniq
+
+ if selects.present?
+ selects.each do |s|
+ @implicit_readonly = false
+ arel = arel.project(s) if s.present?
+ end
+ elsif joins.present?
+ arel = arel.project(@klass.quoted_table_name + '.*')
+ end
+
+ arel = arel.from(@from_value) if @from_value.present?
+
+ case @lock_value
+ when TrueClass
+ arel = arel.lock
+ when String
+ arel = arel.lock(@lock_value)
+ end
+
+ arel
end
- def where(*args)
- return spawn if args.blank?
+ def build_where(*args)
+ return if args.blank?
- builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass))
+ builder = PredicateBuilder.new(table.engine)
conditions = if [String, Array].include?(args.first.class)
merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
@@ -120,7 +176,7 @@ module ActiveRecord
args.first
end
- conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions))
+ conditions
end
private
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index a637e97155..f4abaae43e 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,75 +1,119 @@
module ActiveRecord
module SpawnMethods
- def spawn(relation = @relation)
- relation = Relation.new(@klass, relation)
- relation.readonly = @readonly
- relation.preload_associations = @preload_associations
- relation.eager_load_associations = @eager_load_associations
- relation.includes_associations = @includes_associations
- relation.table = table
+ def spawn(arel_table = self.table)
+ relation = Relation.new(@klass, arel_table)
+
+ (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method|
+ relation.send(:"#{query_method}_values=", send(:"#{query_method}_values"))
+ end
+
+ Relation::SINGLE_VALUE_METHODS.each do |query_method|
+ relation.send(:"#{query_method}_value=", send(:"#{query_method}_value"))
+ end
+
relation
end
def merge(r)
- raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
-
- merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations)
- merged_relation.readonly = r.readonly
-
- [self.relation, r.relation].each do |arel|
- merged_relation = merged_relation.
- joins(arel.joins(arel)).
- group(arel.groupings).
- limit(arel.taken).
- offset(arel.skipped).
- select(arel.send(:select_clauses)).
- from(arel.sources).
- having(arel.havings).
- lock(arel.locked)
+ if r.klass != @klass
+ raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation"
end
- relation_order = r.send(:order_clause)
- merged_order = relation_order.present? ? relation_order : order_clause
- merged_relation = merged_relation.order(merged_order)
+ merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values)
+
+ merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil?
+ merged_relation.limit_value = r.limit_value if r.limit_value.present?
+ merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
+ merged_relation.offset_value = r.offset_value if r.offset_value.present?
+
+ merged_relation = merged_relation.
+ joins(r.joins_values).
+ group(r.group_values).
+ select(r.select_values).
+ from(r.from_value).
+ having(r.having_values)
- merged_wheres = @relation.wheres
+ merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values)
- r.wheres.each do |w|
+ merged_relation.create_with_value = @create_with_value
+
+ if @create_with_value && r.create_with_value
+ merged_relation.create_with_value = @create_with_value.merge(r.create_with_value)
+ else
+ merged_relation.create_with_value = r.create_with_value || @create_with_value
+ end
+
+ merged_wheres = @where_values
+
+ r.where_values.each do |w|
if w.is_a?(Arel::Predicates::Equality)
merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
end
- merged_wheres << w
+ merged_wheres += [w]
end
- merged_relation.where(*merged_wheres)
+ merged_relation.where_values = merged_wheres
+
+ merged_relation
end
alias :& :merge
def except(*skips)
result = Relation.new(@klass, table)
- result.table = table
- [:eager_load, :preload, :includes].each do |load_method|
- result = result.send(load_method, send(:"#{load_method}_associations"))
+ (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method|
+ result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method)
+ end
+
+ Relation::SINGLE_VALUE_METHODS.each do |method|
+ result.send(:"#{method}_value=", send(:"#{method}_value")) unless skips.include?(method)
end
- result.readonly = self.readonly unless skips.include?(:readonly)
+ result
+ end
- result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins)
- result = result.group(@relation.groupings) unless skips.include?(:group)
- result = result.limit(@relation.taken) unless skips.include?(:limit)
- result = result.offset(@relation.skipped) unless skips.include?(:offset)
- result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select)
- result = result.from(@relation.sources) unless skips.include?(:from)
- result = result.order(order_clause) unless skips.include?(:order)
- result = result.where(*@relation.wheres) unless skips.include?(:where)
- result = result.having(*@relation.havings) unless skips.include?(:having)
- result = result.lock(@relation.locked) unless skips.include?(:lock)
+ def only(*onlies)
+ result = Relation.new(@klass, table)
+
+ onlies.each do |only|
+ if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only)
+ result.send(:"#{only}_values=", send(:"#{only}_values"))
+ elsif Relation::SINGLE_VALUE_METHODS.include?(only)
+ result.send(:"#{only}_value=", send(:"#{only}_value"))
+ else
+ raise "Invalid argument : #{only}"
+ end
+ end
result
end
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
+ :order, :select, :readonly, :group, :having, :from, :lock ]
+
+ def apply_finder_options(options)
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
+
+ relation = spawn
+
+ relation = relation.joins(options[:joins]).
+ where(options[:conditions]).
+ select(options[:select]).
+ group(options[:group]).
+ having(options[:having]).
+ order(options[:order]).
+ limit(options[:limit]).
+ offset(options[:offset]).
+ from(options[:from]).
+ includes(options[:include])
+
+ relation = relation.lock(options[:lock]) if options[:lock].present?
+ relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly)
+
+ relation
+ end
+
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 8a32cf1ca2..a996a0ebac 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -28,6 +28,10 @@ module ActiveRecord
class Schema < Migration
private_class_method :new
+ def self.migrations_path
+ ActiveRecord::Migrator.migrations_path
+ end
+
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -44,7 +48,7 @@ module ActiveRecord
unless info[:version].blank?
initialize_schema_migrations_table
- assume_migrated_upto_version info[:version]
+ assume_migrated_upto_version(info[:version], migrations_path)
end
end
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 2dfe2c09ea..0a77ad5fd7 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,5 +1,3 @@
-require "active_support/test_case"
-
module ActiveRecord
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 12c1f23763..d5adcba3ba 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
- super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
+ super(I18n.t('errors.messages.record_invalid', :errors => errors))
end
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 66b78682ad..e41635134c 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -40,8 +40,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
- options = attr_names.extract_options!
- validates_with AssociatedValidator, options.merge(:attributes => attr_names)
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 7efd312357..e28808df98 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -2,13 +2,17 @@ module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator
def initialize(options)
- @klass = options.delete(:klass)
super(options.reverse_merge(:case_sensitive => true))
end
+ # Unfortunately, we have to tie Uniqueness validators to a class.
+ def setup(klass)
+ @klass = klass
+ end
+
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
- table = finder_class.active_relation
+ table = finder_class.unscoped
table_name = record.class.quoted_table_name
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
@@ -170,8 +174,7 @@ module ActiveRecord
# such a case.
#
def validates_uniqueness_of(*attr_names)
- options = attr_names.extract_options!
- validates_with UniquenessValidator, options.merge(:attributes => attr_names, :klass => self)
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
end
end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 18a1cd3cd0..43abcae75e 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -8,39 +8,11 @@ require 'models/categorization'
class InnerJoinAssociationTest < ActiveRecord::TestCase
fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations
- def test_construct_finder_sql_creates_inner_joins
- sql = Author.joins(:posts).to_sql
- assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
- end
-
- def test_construct_finder_sql_cascades_inner_joins
- sql = Author.joins(:posts => :comments).to_sql
- assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
- assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = posts.id/, sql
- end
-
- def test_construct_finder_sql_inner_joins_through_associations
- sql = Author.joins(:categorized_posts).to_sql
- assert_match /INNER JOIN .?categorizations.?.*INNER JOIN .?posts.?/, sql
- end
-
- def test_construct_finder_sql_applies_association_conditions
- sql = Author.joins(:categories_like_general).where("TERMINATING_MARKER").to_sql
- assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?(.|\n)*TERMINATING_MARKER/, sql
- end
-
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.joins(:thinking_posts, :welcome_posts).to_a
assert_equal authors(:david), result.first
end
- def test_construct_finder_sql_unpacks_nested_joins
- sql = Author.joins(:posts => [[:comments]]).to_sql
- assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present"
- assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
- assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = .?posts.?.id/, sql
- end
-
def test_construct_finder_sql_ignores_empty_joins_hash
sql = Author.joins({}).to_sql
assert_no_match /JOIN/i, sql
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index cf763d730a..cc36a6dc5b 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -3,6 +3,8 @@ require 'models/bird'
require 'models/company'
require 'models/customer'
require 'models/developer'
+require 'models/invoice'
+require 'models/line_item'
require 'models/order'
require 'models/parrot'
require 'models/person'
@@ -699,23 +701,18 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
- before = @pirate.send(association_name).map { |c| c }
+ before = @pirate.send(association_name).map { |c| c.mark_for_destruction ; c }
- # Stub the save method of the first child to destroy and the second to raise an exception
- class << before.first
- def save(*args)
- super
- destroy
- end
- end
+ # Stub the destroy method of the the second child to raise an exception
class << before.last
- def save(*args)
+ def destroy(*args)
super
raise 'Oh noes!'
end
end
assert_raise(RuntimeError) { assert !@pirate.save }
+ assert before.first.frozen? # the first child was indeed destroyed
assert_equal before, @pirate.reload.send(association_name)
end
@@ -797,6 +794,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
assert @pirate.errors[:catchphrase].any?
end
+ def test_should_not_ignore_different_error_messages_on_the_same_attribute
+ Ship.validates_format_of :name, :with => /\w/
+ @pirate.ship.name = ""
+ @pirate.catchphrase = nil
+ assert @pirate.invalid?
+ assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
+ end
+
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@pirate.catchphrase = ''
@pirate.ship.name = ''
@@ -833,6 +838,18 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
end
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
+ pirate = Pirate.new(:catchphrase => 'Arr')
+ ship = pirate.build_ship(:name => 'The Vile Insanity')
+ ship.cancel_save_from_callback = true
+
+ assert_no_difference 'Pirate.count' do
+ assert_no_difference 'Ship.count' do
+ assert !pirate.save
+ end
+ end
+ end
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
before = [@pirate.catchphrase, @pirate.ship.name]
@@ -916,6 +933,18 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
end
end
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
+ ship = Ship.new(:name => 'The Vile Insanity')
+ pirate = ship.build_pirate(:catchphrase => 'Arr')
+ pirate.cancel_save_from_callback = true
+
+ assert_no_difference 'Ship.count' do
+ assert_no_difference 'Pirate.count' do
+ assert !ship.save
+ end
+ end
+ end
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
before = [@ship.pirate.catchphrase, @ship.name]
@@ -931,7 +960,6 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
end
assert_raise(RuntimeError) { assert !@ship.save }
- # TODO: Why does using reload on @ship looses the associated pirate?
assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
end
@@ -974,9 +1002,9 @@ module AutosaveAssociationOnACollectionAssociationTests
end
def test_should_default_invalid_error_from_i18n
- I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
+ I18n.backend.store_translations(:en, :errors => { :models =>
{ @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
- }})
+ })
@pirate.send(@association_name).build(:name => '')
@@ -985,9 +1013,7 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages
assert @pirate.errors[@association_name].empty?
ensure
- I18n.backend.store_translations(:en, :activerecord => { :errors => { :models =>
- { @association_name.to_s.singularize.to_sym => nil }
- }})
+ I18n.backend = I18n::Backend::Simple.new
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@@ -1034,6 +1060,26 @@ module AutosaveAssociationOnACollectionAssociationTests
end
end
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update
+ @pirate.catchphrase = 'Changed'
+ @child_1.name = 'Changed'
+ @child_1.cancel_save_from_callback = true
+
+ assert !@pirate.save
+ assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase
+ assert_equal "Posideons Killer", @child_1.reload.name
+
+ new_pirate = Pirate.new(:catchphrase => 'Arr')
+ new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley')
+ new_child.cancel_save_from_callback = true
+
+ assert_no_difference 'Pirate.count' do
+ assert_no_difference "#{new_child.class.name}.count" do
+ assert !new_pirate.save
+ end
+ end
+ end
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
new_names = ['Grace OMalley', 'Privateers Greed']
@@ -1217,3 +1263,10 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrots)
end
end
+
+class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
+ def test_autosave_with_touch_should_not_raise_system_stack_error
+ invoice = Invoice.create
+ assert_nothing_raised { invoice.line_items.create(:amount => 10) }
+ end
+end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 730d9d8df7..aea6aed8d9 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -992,7 +992,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_mass_assignment_protection_against_class_attribute_writers
- [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging,
+ [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
:default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
assert Task.respond_to?(method)
assert Task.respond_to?("#{method}=")
@@ -2138,8 +2138,11 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_type_name_with_module_should_handle_beginning
+ ActiveRecord::Base.store_full_sti_class = false
assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_to_param_should_return_string
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 5a084a611e..ff2322ac15 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -113,6 +113,26 @@ class ImmutableMethodDeveloper < ActiveRecord::Base
end
end
+class OnCallbacksDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ before_validation { history << :before_validation }
+ before_validation(:on => :create){ history << :before_validation_on_create }
+ before_validation(:on => :update){ history << :before_validation_on_update }
+
+ validate do
+ history << :validate
+ end
+
+ after_validation { history << :after_validation }
+ after_validation(:on => :create){ history << :after_validation_on_create }
+ after_validation(:on => :update){ history << :after_validation_on_update }
+
+ def history
+ @history ||= []
+ end
+end
+
class CallbackCancellationDeveloper < ActiveRecord::Base
set_table_name 'developers'
@@ -250,7 +270,18 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_save
+ def test_validate_on_create
+ david = OnCallbacksDeveloper.create('name' => 'David', 'salary' => 1000000)
+ assert_equal [
+ :before_validation,
+ :before_validation_on_create,
+ :validate,
+ :after_validation,
+ :after_validation_on_create
+ ], david.history
+ end
+
+ def test_update
david = CallbackDeveloper.find(1)
david.save
assert_equal [
@@ -297,6 +328,18 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
+ def test_validate_on_update
+ david = OnCallbacksDeveloper.find(1)
+ david.save
+ assert_equal [
+ :before_validation,
+ :before_validation_on_update,
+ :validate,
+ :after_validation,
+ :after_validation_on_update
+ ], david.history
+ end
+
def test_destroy
david = CallbackDeveloper.find(1)
david.destroy
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index eb3f03c91d..f965652a9a 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -519,8 +519,8 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_identifies_consistently
- assert_equal 1281023246, Fixtures.identify(:ruby)
- assert_equal 2140105598, Fixtures.identify(:sapphire_2)
+ assert_equal 207281424, Fixtures.identify(:ruby)
+ assert_equal 1066363776, Fixtures.identify(:sapphire_2)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 479970b2fa..fa76e2d57a 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -13,8 +13,6 @@ require 'test/unit'
require 'stringio'
require 'active_record'
-require 'active_record/test_case'
-require 'active_record/fixtures'
require 'connection'
begin
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 73e51fbd91..0672fb938b 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -241,6 +241,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
end
def test_instantiation_doesnt_try_to_require_corresponding_file
+ ActiveRecord::Base.store_full_sti_class = false
foo = Firm.find(:first).clone
foo.ruby_type = foo.type = 'FirmOnTheFly'
foo.save!
@@ -259,5 +260,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
# And instantiate will find the existing constant rather than trying
# to require firm_on_the_fly.
assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index cfc6f8772c..26aa3ed8d5 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -11,7 +11,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_set_conditions
Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do
- assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions]
+ assert_equal '(just a test...)', Developer.scoped.send(:where_clause)
end
end
@@ -207,7 +207,7 @@ class MethodScopingTest < ActiveRecord::TestCase
new_comment = nil
VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
- assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create])
+ assert_equal({:post_id => 1}, VerySpecialComment.scoped.send(:scope_for_create))
new_comment = VerySpecialComment.create :body => "Wonderful world"
end
@@ -256,8 +256,9 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merge_options
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
Developer.send(:with_scope, :find => { :limit => 10 }) do
- merged_option = Developer.instance_eval('current_scoped_methods')[:find]
- assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option)
+ devs = Developer.scoped
+ assert_equal '(salary = 80000)', devs.send(:where_clause)
+ assert_equal 10, devs.taken
end
end
end
@@ -265,26 +266,26 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merge_inner_scope_has_priority
Developer.send(:with_scope, :find => { :limit => 5 }) do
Developer.send(:with_scope, :find => { :limit => 10 }) do
- merged_option = Developer.instance_eval('current_scoped_methods')[:find]
- assert_equal({ :limit => 10 }, merged_option)
+ assert_equal 10, Developer.scoped.taken
end
end
end
def test_replace_options
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'Jamis'" }) do
- assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods'))
- assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1])
+ Developer.send(:with_scope, :find => { :conditions => {:name => 'David'} }) do
+ Developer.send(:with_exclusive_scope, :find => { :conditions => {:name => 'Jamis'} }) do
+ assert_equal 'Jamis', Developer.scoped.send(:scope_for_create)[:name]
end
+
+ assert_equal 'David', Developer.scoped.send(:scope_for_create)[:name]
end
end
def test_append_conditions
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
- appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions]
- assert_equal("(name = 'David') AND (salary = 80000)", appended_condition)
+ devs = Developer.scoped
+ assert_equal "(name = 'David') AND (salary = 80000)", devs.send(:where_clause)
assert_equal(1, Developer.count)
end
Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
@@ -296,8 +297,9 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merge_and_append_options
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- merged_option = Developer.instance_eval('current_scoped_methods')[:find]
- assert_equal({ :conditions => "(salary = 80000) AND (name = 'David')", :limit => 10 }, merged_option)
+ devs = Developer.scoped
+ assert_equal "(salary = 80000) AND (name = 'David')", devs.send(:where_clause)
+ assert_equal 10, devs.taken
end
end
end
@@ -325,15 +327,15 @@ class NestedScopingTest < ActiveRecord::TestCase
# :include's remain unique and don't "double up" when merging
Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
- assert_equal('David', Developer.find(:first).name)
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
+ assert_equal 'David', Developer.find(:first).name
end
end
# the nested scope doesn't remove the first :include
Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
Developer.send(:with_scope, :find => { :include => [] }) do
- assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
assert_equal('David', Developer.find(:first).name)
end
end
@@ -341,7 +343,7 @@ class NestedScopingTest < ActiveRecord::TestCase
# mixing array and symbol include's will merge correctly
Developer.send(:with_scope, :find => { :include => [:projects], :conditions => "projects.id = 2" }) do
Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal 1, Developer.scoped.includes_values.uniq.length
assert_equal('David', Developer.find(:first).name)
end
end
@@ -350,7 +352,7 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_nested_scoped_find_replace_include
Developer.send(:with_scope, :find => { :include => :projects }) do
Developer.send(:with_exclusive_scope, :find => { :include => [] }) do
- assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal 0, Developer.scoped.includes_values.length
end
end
end
@@ -416,7 +418,7 @@ class NestedScopingTest < ActiveRecord::TestCase
comment = nil
Comment.send(:with_scope, :create => { :post_id => 1}) do
Comment.send(:with_scope, :create => { :post_id => 2}) do
- assert_equal({ :post_id => 2 }, Comment.send(:current_scoped_methods)[:create])
+ assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
end
end
@@ -425,9 +427,11 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_nested_exclusive_scope_for_create
comment = nil
+
Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do
Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
- assert_equal({ :post_id => 1 }, Comment.send(:current_scoped_methods)[:create])
+ assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
+ assert Comment.new.body.blank?
comment = Comment.create :body => "Hey guys"
end
end
@@ -603,44 +607,39 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_default_scoping_with_threads
- scope = [{ :create => {}, :find => { :order => 'salary DESC' } }]
-
2.times do
- Thread.new { assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods) }.join
+ Thread.new { assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) }.join
end
end
def test_default_scoping_with_inheritance
- scope = [{ :create => {}, :find => { :order => 'salary DESC' } }]
-
# Inherit a class having a default scope and define a new default scope
klass = Class.new(DeveloperOrderedBySalary)
klass.send :default_scope, {}
# Scopes added on children should append to parent scope
- expected_klass_scope = [{ :create => {}, :find => { :order => 'salary DESC' }}, { :create => {}, :find => {} }]
- assert_equal expected_klass_scope, klass.send(:scoped_methods)
+ assert klass.scoped.send(:order_clause).blank?
# Parent should still have the original scope
- assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods)
+ assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause)
end
def test_method_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
assert_equal expected, received
end
def test_nested_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
assert_equal expected, received
end
- def test_named_scope_overwrites_default
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ def test_named_scope_order_appended_to_default_scope_order
+ expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.name }
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 4f559bcaa5..d781a229f4 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -12,6 +12,8 @@ class ModulesTest < ActiveRecord::TestCase
[:Firm, :Client].each do |const|
@undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const)
end
+
+ ActiveRecord::Base.store_full_sti_class = false
end
def teardown
@@ -19,6 +21,8 @@ class ModulesTest < ActiveRecord::TestCase
@undefined_consts.each do |constant, value|
Object.send :const_set, constant, value unless value.nil?
end
+
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_module_spanning_associations
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 6155bfd50a..bd51388e05 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -85,7 +85,7 @@ class MultipleDbTest < ActiveRecord::TestCase
end
def test_arel_table_engines
- assert_not_equal Entrant.active_relation_engine, Course.active_relation_engine
- assert_equal Entrant.active_relation_engine, Bird.active_relation_engine
+ assert_not_equal Entrant.arel_engine, Course.arel_engine
+ assert_equal Entrant.arel_engine, Bird.arel_engine
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 8891282915..7ca9c416cb 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -34,7 +34,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_add_a_proc_to_nested_attributes_options
- [:parrots, :birds, :birds_with_reject_all_blank].each do |name|
+ assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC,
+ Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if]
+
+ [:parrots, :birds].each do |name|
assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
end
end
@@ -79,12 +82,6 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert ship._destroy
end
- def test_underscore_delete_is_deprecated
- ActiveSupport::Deprecation.expects(:warn)
- ship = Ship.create!(:name => 'Nights Dirty Lightning')
- ship._delete
- end
-
def test_reject_if_method_without_arguments
Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
@@ -176,6 +173,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ @pirate.ship_attributes = { :id => 1234567890 }
+ end
+ end
+
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
@@ -269,6 +272,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
+ include AssertRaiseWithMessage
+
def setup
@ship = Ship.new(:name => 'Nights Dirty Lightning')
@pirate = @ship.build_pirate(:catchphrase => 'Aye')
@@ -323,6 +328,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert_equal 'Arr', @ship.pirate.catchphrase
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
+ @ship.pirate_attributes = { :id => 1234567890 }
+ end
+ end
+
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
@@ -384,10 +395,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert Ship.reflect_on_association(:pirate).options[:autosave]
end
- def test_should_accept_update_only_option
- @ship.update_attribute(:update_only_pirate_attributes, { :id => @pirate.ship.id, :catchphrase => 'Arr' })
- end
-
def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
@pirate.delete
assert_difference('Pirate.count', 1) do
@@ -460,6 +467,12 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
+ end
+ end
+
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@pirate.send(@association_name).destroy_all
@pirate.reload.attributes = {
@@ -565,7 +578,7 @@ module NestedAttributesOnACollectionAssociationTests
assert Pirate.reflect_on_association(@association_name).options[:autosave]
end
- def test_validate_presence_of_parent__works_with_inverse_of
+ def test_validate_presence_of_parent_works_with_inverse_of
Man.accepts_nested_attributes_for(:interests)
assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
@@ -582,7 +595,7 @@ module NestedAttributesOnACollectionAssociationTests
end
end
- def test_validate_presence_of_parent__fails_without_inverse_of
+ def test_validate_presence_of_parent_fails_without_inverse_of
Man.accepts_nested_attributes_for(:interests)
Man.reflect_on_association(:interests).options.delete(:inverse_of)
Interest.reflect_on_association(:man).options.delete(:inverse_of)
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 2529a33dab..94d6663778 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -61,12 +61,14 @@ class PooledConnectionsTest < ActiveRecord::TestCase
checkout_checkin_connections 1, 2
assert_equal 2, @connection_count
assert_equal 0, @timed_out
+ assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
end
def test_pooled_connection_checkin_two
checkout_checkin_connections 2, 3
assert_equal 3, @connection_count
assert_equal 0, @timed_out
+ assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
end
def test_pooled_connection_checkout_existing_first
@@ -135,15 +137,4 @@ class PooledConnectionsTest < ActiveRecord::TestCase
def add_record(name)
ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
end
-end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
-
-class AllowConcurrencyDeprecatedTest < ActiveRecord::TestCase
- def test_allow_concurrency_is_deprecated
- assert_deprecated('ActiveRecord::Base.allow_concurrency') do
- ActiveRecord::Base.allow_concurrency
- end
- assert_deprecated('ActiveRecord::Base.allow_concurrency=') do
- ActiveRecord::Base.allow_concurrency = true
- end
- end
-end
+end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name \ No newline at end of file
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index acd214eb5a..2c9158aa7b 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -4,10 +4,13 @@ require 'models/customer'
require 'models/company'
require 'models/company_in_module'
require 'models/subscriber'
+require 'models/ship'
require 'models/pirate'
require 'models/price_estimate'
class ReflectionTest < ActiveRecord::TestCase
+ include ActiveRecord::Reflection
+
fixtures :topics, :customers, :companies, :subscribers, :price_estimates
def setup
@@ -68,22 +71,22 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_klass_for_nested_class_name
- reflection = ActiveRecord::Reflection::MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil)
+ reflection = MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil)
assert_nothing_raised do
assert_equal MyApplication::Business::Company, reflection.klass
end
end
def test_aggregation_reflection
- reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_address = AggregateReflection.new(
:composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
)
- reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_balance = AggregateReflection.new(
:composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
)
- reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new(
+ reflection_for_gps_location = AggregateReflection.new(
:composed_of, :gps_location, { }, Customer
)
@@ -108,7 +111,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_many_reflection
- reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
+ reflection_for_clients = AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
@@ -120,7 +123,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_one_reflection
- reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
+ reflection_for_account = AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
assert_equal reflection_for_account, Firm.reflect_on_association(:account)
assert_equal Account, Firm.reflect_on_association(:account).klass
@@ -137,6 +140,8 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_association_reflection_in_modules
+ ActiveRecord::Base.store_full_sti_class = false
+
assert_reflection MyApplication::Business::Firm,
:clients_of_firm,
:klass => MyApplication::Business::Client,
@@ -172,6 +177,8 @@ class ReflectionTest < ActiveRecord::TestCase
:klass => MyApplication::Billing::Nested::Firm,
:class_name => 'Nested::Firm',
:table_name => 'companies'
+ ensure
+ ActiveRecord::Base.store_full_sti_class = true
end
def test_reflection_of_all_associations
@@ -187,7 +194,44 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_many_through_reflection
- assert_kind_of ActiveRecord::Reflection::ThroughReflection, Subscriber.reflect_on_association(:books)
+ assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
+ end
+
+ def test_collection_association
+ assert Pirate.reflect_on_association(:birds).collection?
+ assert Pirate.reflect_on_association(:parrots).collection?
+
+ assert !Pirate.reflect_on_association(:ship).collection?
+ assert !Ship.reflect_on_association(:pirate).collection?
+ end
+
+ def test_default_association_validation
+ assert AssociationReflection.new(:has_many, :clients, {}, Firm).validate?
+
+ assert !AssociationReflection.new(:has_one, :client, {}, Firm).validate?
+ assert !AssociationReflection.new(:belongs_to, :client, {}, Firm).validate?
+ assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, {}, Firm).validate?
+ end
+
+ def test_always_validate_association_if_explicit
+ assert AssociationReflection.new(:has_one, :client, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:belongs_to, :client, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:has_many, :clients, { :validate => true }, Firm).validate?
+ assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :validate => true }, Firm).validate?
+ end
+
+ def test_validate_association_if_autosave
+ assert AssociationReflection.new(:has_one, :client, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:belongs_to, :client, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:has_many, :clients, { :autosave => true }, Firm).validate?
+ assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true }, Firm).validate?
+ end
+
+ def test_never_validate_association_if_explicit
+ assert !AssociationReflection.new(:has_one, :client, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:belongs_to, :client, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:has_many, :clients, { :autosave => true, :validate => false }, Firm).validate?
+ assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true, :validate => false }, Firm).validate?
end
private
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index f895f8b8d2..195889f1df 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -545,6 +545,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 'hen', hen.name
end
+ def test_explicit_create_scope
+ hens = Bird.where(:name => 'hen')
+ assert_equal 'hen', hens.new.name
+
+ hens = hens.create_with(:name => 'cock')
+ assert_equal 'cock', hens.new.name
+ end
+
def test_except
relation = Post.where(:author_id => 1).order('id ASC').limit(1)
assert_equal [posts(:welcome)], relation.all
diff --git a/activerecord/test/cases/subscriber_test.rb b/activerecord/test/cases/subscriber_test.rb
new file mode 100644
index 0000000000..ce91d9385d
--- /dev/null
+++ b/activerecord/test/cases/subscriber_test.rb
@@ -0,0 +1,51 @@
+require "cases/helper"
+require "models/developer"
+require "rails/subscriber/test_helper"
+require "active_record/railties/subscriber"
+
+module SubscriberTest
+ Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new)
+
+ def setup
+ @old_logger = ActiveRecord::Base.logger
+ super
+ end
+
+ def teardown
+ super
+ ActiveRecord::Base.logger = @old_logger
+ end
+
+ def set_logger(logger)
+ ActiveRecord::Base.logger = logger
+ end
+
+ def test_basic_query_logging
+ Developer.all
+ wait
+ assert_equal 1, @logger.logged(:debug).size
+ assert_match /Developer Load/, @logger.logged(:debug).last
+ assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last
+ end
+
+ def test_cached_queries
+ ActiveRecord::Base.cache do
+ Developer.all
+ Developer.all
+ end
+ wait
+ assert_equal 2, @logger.logged(:debug).size
+ assert_match /CACHE/, @logger.logged(:debug).last
+ assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last
+ end
+
+ class SyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include SubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include SubscriberTest
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 5ed997356b..1246dd4276 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -10,33 +10,29 @@ require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
fixtures :topics, :owners
- repair_validations(Topic, Reply)
+ repair_validations(Topic, Reply, Owner)
def test_validates_size_of_association
- repair_validations(Owner) do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
- pet = o.pets.build('name' => 'apet')
- assert o.valid?
- end
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
end
def test_validates_size_of_association_using_within
- repair_validations(Owner) do
- assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
+ assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
- pet = o.pets.build('name' => 'apet')
- assert o.valid?
+ pet = o.pets.build('name' => 'apet')
+ assert o.valid?
- 2.times { o.pets.build('name' => 'apet') }
- assert !o.save
- assert o.errors[:pets].any?
- end
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors[:pets].any?
end
def test_validates_associated_many
@@ -55,51 +51,43 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_one
- repair_validations(Reply) do
- Reply.validates_associated( :topic )
- Topic.validates_presence_of( :content )
- r = Reply.new("title" => "A reply", "content" => "with content!")
- r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
- assert r.errors[:topic].any?
- r.topic.content = "non-empty"
- assert r.valid?
- end
+ Reply.validates :topic, :associated => true
+ Topic.validates_presence_of( :content )
+ r = Reply.new("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert r.errors[:topic].any?
+ r.topic.content = "non-empty"
+ assert r.valid?
end
def test_validates_associated_with_custom_message_using_quotes
- repair_validations(Reply) do
- Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
- Topic.validates_presence_of :content
- r = Reply.create("title" => "A reply", "content" => "with content!")
- r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
- assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
- end
+ Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
+ Topic.validates_presence_of :content
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
end
def test_validates_associated_missing
- repair_validations(Reply) do
- Reply.validates_presence_of(:topic)
- r = Reply.create("title" => "A reply", "content" => "with content!")
- assert !r.valid?
- assert r.errors[:topic].any?
+ Reply.validates_presence_of(:topic)
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ assert !r.valid?
+ assert r.errors[:topic].any?
- r.topic = Topic.find :first
- assert r.valid?
- end
+ r.topic = Topic.find :first
+ assert r.valid?
end
def test_validates_size_of_association_utf8
- repair_validations(Owner) do
- with_kcode('UTF8') do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'あいうえおかきくけこ')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'あいうえおかきくけこ')
- assert o.valid?
- end
+ with_kcode('UTF8') do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
end
end
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 3f96d7973b..15730c2a87 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -6,15 +6,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
def setup
Topic.reset_callbacks(:validate)
@topic = Topic.new
- I18n.backend.store_translations :'en', {
- :activerecord => {
- :errors => {
- :messages => {
- :taken => "has already been taken",
- }
- }
- }
- }
+ I18n.backend = I18n::Backend::Simple.new
end
# validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index f017f24048..5dfbb1516f 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -4,13 +4,14 @@ require 'models/reply'
class I18nValidationTest < ActiveRecord::TestCase
repair_validations(Topic, Reply)
+
def setup
Reply.validates_presence_of(:title)
@topic = Topic.new
- @old_load_path, @old_backend = I18n.load_path, I18n.backend
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}})
+ I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}})
end
def teardown
@@ -30,75 +31,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # ActiveRecord::Errors
- def test_errors_generate_message_translates_custom_model_attribute_key
- I18n.expects(:translate).with(
- :topic,
- { :count => 1,
- :default => ['Topic'],
- :scope => [:activerecord, :models]
- }
- ).returns('Topic')
-
- I18n.expects(:translate).with(
- :"topic.title",
- { :count => 1,
- :default => ['Title'],
- :scope => [:activerecord, :attributes]
- }
- ).returns('Title')
-
- I18n.expects(:translate).with(
- :"models.topic.attributes.title.invalid",
- :value => nil,
- :scope => [:activerecord, :errors],
- :default => [
- :"models.topic.invalid",
- 'default from class def error 1',
- :"messages.invalid"],
- :attribute => "Title",
- :model => "Topic"
- ).returns('default from class def error 1')
-
- @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1'
- end
-
- def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
-
- I18n.expects(:translate).with(
- :reply,
- { :count => 1,
- :default => [:topic, 'Reply'],
- :scope => [:activerecord, :models]
- }
- ).returns('Reply')
-
- I18n.expects(:translate).with(
- :"reply.title",
- { :count => 1,
- :default => [:'topic.title', 'Title'],
- :scope => [:activerecord, :attributes]
- }
- ).returns('Title')
-
- I18n.expects(:translate).with(
- :"models.reply.attributes.title.invalid",
- :value => nil,
- :scope => [:activerecord, :errors],
- :default => [
- :"models.reply.invalid",
- :"models.topic.attributes.title.invalid",
- :"models.topic.invalid",
- 'default from class def',
- :"messages.invalid"],
- :model => 'Reply',
- :attribute => 'Title'
- ).returns("default from class def")
-
- Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
-
- end
-
# validates_uniqueness_of w/ mocha
def test_validates_uniqueness_of_generates_message
@@ -115,6 +47,25 @@ class I18nValidationTest < ActiveRecord::TestCase
@topic.valid?
end
+ # validates_uniqueness_of w/o mocha
+
+ def test_validates_associated_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
+
+ Topic.validates_uniqueness_of :title
+ unique_topic.valid?
+ assert_equal ['custom message'], unique_topic.errors[:replies]
+ end
+
+ def test_validates_associated_finds_global_default_translation
+ I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
+
+ Topic.validates_uniqueness_of :title
+ unique_topic.valid?
+ assert_equal ['global message'], unique_topic.errors[:replies]
+ end
+
# validates_associated w/ mocha
def test_validates_associated_generates_message
@@ -132,8 +83,8 @@ class I18nValidationTest < ActiveRecord::TestCase
# validates_associated w/o mocha
def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -141,7 +92,7 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_finds_global_default_translation
- I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 8f84841fe6..9a863c25a8 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -38,7 +38,7 @@ end
class UniquenessValidationTest < ActiveRecord::TestCase
fixtures :topics, 'warehouse-things', :developers
- repair_validations(Topic)
+ repair_validations(Topic, Reply)
def test_validate_uniqueness
Topic.validates_uniqueness_of(:title)
@@ -58,6 +58,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.save, "Should now save t2 as unique"
end
+ def test_validates_uniqueness_with_validates
+ Topic.validates :title, :uniqueness => true
+ t = Topic.create!('title' => 'abc')
+
+ t2 = Topic.new('title' => 'abc')
+ assert !t2.valid?
+ assert t2.errors[:title]
+ end
+
def test_validates_uniqueness_with_newline_chars
Topic.validates_uniqueness_of(:title, :case_sensitive => false)
@@ -66,24 +75,22 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_scope
- repair_validations(Reply) do
- Reply.validates_uniqueness_of(:content, :scope => "parent_id")
+ Reply.validates_uniqueness_of(:content, :scope => "parent_id")
- t = Topic.create("title" => "I'm unique!")
+ t = Topic.create("title" => "I'm unique!")
- r1 = t.replies.create "title" => "r1", "content" => "hello world"
- assert r1.valid?, "Saving r1"
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
- r2 = t.replies.create "title" => "r2", "content" => "hello world"
- assert !r2.valid?, "Saving r2 first time"
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
- r2.content = "something else"
- assert r2.save, "Saving r2 second time"
+ r2.content = "something else"
+ assert r2.save, "Saving r2 second time"
- t2 = Topic.create("title" => "I'm unique too!")
- r3 = t2.replies.create "title" => "r3", "content" => "hello world"
- assert r3.valid?, "Saving r3"
- end
+ t2 = Topic.create("title" => "I'm unique too!")
+ r3 = t2.replies.create "title" => "r3", "content" => "hello world"
+ assert r3.valid?, "Saving r3"
end
def test_validate_uniqueness_scoped_to_defining_class
@@ -102,29 +109,27 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_scope_array
- repair_validations(Reply) do
- Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
+ Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
- t = Topic.create("title" => "The earth is actually flat!")
+ t = Topic.create("title" => "The earth is actually flat!")
- r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
- assert r1.valid?, "Saving r1"
+ r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
+ assert r1.valid?, "Saving r1"
- r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
- assert !r2.valid?, "Saving r2. Double reply by same author."
+ r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
+ assert !r2.valid?, "Saving r2. Double reply by same author."
- r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
- assert r2.save, "Saving r2 the second time."
+ r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
+ assert r2.save, "Saving r2 the second time."
- r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
- assert !r3.valid?, "Saving r3"
+ r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
+ assert !r3.valid?, "Saving r3"
- r3.author_name = "jj"
- assert r3.save, "Saving r3 the second time."
+ r3.author_name = "jj"
+ assert r3.save, "Saving r3 the second time."
- r3.author_name = "jeremy"
- assert !r3.save, "Saving r3 the third time."
- end
+ r3.author_name = "jeremy"
+ assert !r3.save, "Saving r3 the third time."
end
def test_validate_case_insensitive_uniqueness
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index 341d2eeffc..e61d48e6a5 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -1,3 +1,9 @@
class Bird < ActiveRecord::Base
validates_presence_of :name
+
+ attr_accessor :cancel_save_from_callback
+ before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
+ def cancel_save_callback_method
+ false
+ end
end \ No newline at end of file
diff --git a/activerecord/test/models/invoice.rb b/activerecord/test/models/invoice.rb
new file mode 100644
index 0000000000..fc6ef0230e
--- /dev/null
+++ b/activerecord/test/models/invoice.rb
@@ -0,0 +1,4 @@
+class Invoice < ActiveRecord::Base
+ has_many :line_items, :autosave => true
+ before_save {|record| record.balance = record.line_items.map(&:amount).sum }
+end
diff --git a/activerecord/test/models/line_item.rb b/activerecord/test/models/line_item.rb
new file mode 100644
index 0000000000..0dd921a300
--- /dev/null
+++ b/activerecord/test/models/line_item.rb
@@ -0,0 +1,3 @@
+class LineItem < ActiveRecord::Base
+ belongs_to :invoice, :touch => true
+end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 4a7ed52636..737ef9131b 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -6,6 +6,12 @@ class Parrot < ActiveRecord::Base
alias_attribute :title, :name
validates_presence_of :name
+
+ attr_accessor :cancel_save_from_callback
+ before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
+ def cancel_save_callback_method
+ false
+ end
end
class LiveParrot < Parrot
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 88c1634717..f1dbe32c6e 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -51,6 +51,12 @@ class Pirate < ActiveRecord::Base
attributes.delete('_reject_me_if_new').present? && new_record?
end
+ attr_accessor :cancel_save_from_callback
+ before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
+ def cancel_save_callback_method
+ false
+ end
+
private
def log_before_add(record)
log(record, "before_adding_method")
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index a96e38ab41..75c792d176 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -9,4 +9,10 @@ class Ship < ActiveRecord::Base
accepts_nested_attributes_for :update_only_pirate, :update_only => true
validates_presence_of :name
+
+ attr_accessor :cancel_save_from_callback
+ before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
+ def cancel_save_callback_method
+ false
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 1ec36e7832..bec4291457 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -191,6 +191,11 @@ ActiveRecord::Schema.define do
t.string :info
end
+ create_table :invoices, :force => true do |t|
+ t.integer :balance
+ t.datetime :updated_at
+ end
+
create_table :items, :force => true do |t|
t.column :name, :integer
end
@@ -216,6 +221,11 @@ ActiveRecord::Schema.define do
t.integer :version, :null => false, :default => 0
end
+ create_table :line_items, :force => true do |t|
+ t.integer :invoice_id
+ t.integer :amount
+ end
+
create_table :lock_without_defaults, :force => true do |t|
t.column :lock_version, :integer
end
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index 193be89a82..2f0ccd7dae 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -103,14 +103,14 @@ module ActiveResource
with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
end
-
private
# Makes a request to the remote service.
def request(method, path, *arguments)
- logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
- result = nil
- ms = Benchmark.ms { result = http.send(method, path, *arguments) }
- logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
+ result = ActiveSupport::Notifications.instrument("active_resource.request") do |payload|
+ payload[:method] = method
+ payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
+ payload[:result] = http.send(method, path, *arguments)
+ end
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
@@ -274,10 +274,6 @@ module ActiveResource
{HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
end
- def logger #:nodoc:
- Base.logger
- end
-
def legitimize_auth_type(auth_type)
return :basic if auth_type.nil?
auth_type = auth_type.to_sym
diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb
index 4f264c82b8..1b9307d472 100644
--- a/activeresource/lib/active_resource/railtie.rb
+++ b/activeresource/lib/active_resource/railtie.rb
@@ -1,2 +1,11 @@
require "active_resource"
-require "rails" \ No newline at end of file
+require "rails"
+
+module ActiveResource
+ class Railtie < Rails::Railtie
+ plugin_name :active_resource
+
+ require "active_resource/railties/subscriber"
+ subscriber ActiveResource::Railties::Subscriber.new
+ end
+end \ No newline at end of file
diff --git a/activeresource/lib/active_resource/railties/subscriber.rb b/activeresource/lib/active_resource/railties/subscriber.rb
new file mode 100644
index 0000000000..fb98061b71
--- /dev/null
+++ b/activeresource/lib/active_resource/railties/subscriber.rb
@@ -0,0 +1,15 @@
+module ActiveResource
+ module Railties
+ class Subscriber < Rails::Subscriber
+ def request(event)
+ result = event.payload[:result]
+ info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
+ info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
+ end
+
+ def logger
+ ActiveResource::Base.logger
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb
index 5fa6d3023b..1e71d5d0dd 100644
--- a/activeresource/test/abstract_unit.rb
+++ b/activeresource/test/abstract_unit.rb
@@ -10,8 +10,6 @@ require 'rubygems'
require 'test/unit'
require 'active_resource'
require 'active_support'
-require 'active_support/test_case'
-require 'active_model/test_case'
$:.unshift "#{File.dirname(__FILE__)}/../test"
require 'setter_trap'
diff --git a/activeresource/test/cases/subscriber_test.rb b/activeresource/test/cases/subscriber_test.rb
new file mode 100644
index 0000000000..7100b02872
--- /dev/null
+++ b/activeresource/test/cases/subscriber_test.rb
@@ -0,0 +1,39 @@
+require "abstract_unit"
+require "fixtures/person"
+require "rails/subscriber/test_helper"
+require "active_resource/railties/subscriber"
+
+module SubscriberTest
+ Rails::Subscriber.add(:active_resource, ActiveResource::Railties::Subscriber.new)
+
+ def setup
+ @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, @matz
+ end
+
+ super
+ end
+
+ def set_logger(logger)
+ ActiveResource::Base.logger = logger
+ end
+
+ def test_request_notification
+ matz = Person.find(1)
+ wait
+ assert_equal 2, @logger.logged(:info).size
+ assert_equal "GET http://somewhere.else:80/people/1.xml", @logger.logged(:info)[0]
+ assert_match /\-\-\> 200 200 106/, @logger.logged(:info)[1]
+ end
+
+ class SyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include SubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include SubscriberTest
+ end
+end \ No newline at end of file
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 9b0a84678a..87ec6f2a2c 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,8 @@
*Edge*
+* Changed the default ActiveSupport.use_standard_json_time_format from false to true and
+ActiveSupport.escape_html_entities_in_json from true to false to match previously announced Rails 3 defaults [DHH]
+
* Added Object#presence that returns the object if it's #present? otherwise returns nil [DHH/Colin Kelley]
* Add Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?/else calls [DHH]
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 2d2cbf6448..8b9dd55b53 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
s.summary = "Support and utility classes used by the Rails framework."
s.description = %q{Utility library which carries commonly used classes and goodies from the Rails framework}
- s.add_dependency('i18n', '>= 0.1.3')
+ s.add_dependency('i18n', '~> 0.3.0')
s.files = Dir['CHANGELOG', 'README', 'lib/**/*']
s.require_path = 'lib'
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 3463000529..51ec87f329 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -66,6 +66,8 @@ module ActiveSupport
autoload :StringInquirer
autoload :XmlMini
end
+
+ autoload :TestCase
end
require 'active_support/vendor'
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index ad238c1d96..6360a4614e 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -247,13 +247,13 @@ module ActiveSupport
expires_in || 0
end
- def instrument(operation, key, options, &block)
+ def instrument(operation, key, options)
log(operation, key, options)
if self.class.instrument
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument(:"cache_#{operation}", payload, &block)
+ ActiveSupport::Notifications.instrument("active_support.cache_#{operation}", payload){ yield }
else
yield
end
diff --git a/activesupport/lib/active_support/core_ext/float/rounding.rb b/activesupport/lib/active_support/core_ext/float/rounding.rb
index 0b1ae4be7e..9bdf5bba7b 100644
--- a/activesupport/lib/active_support/core_ext/float/rounding.rb
+++ b/activesupport/lib/active_support/core_ext/float/rounding.rb
@@ -1,5 +1,6 @@
class Float
- remove_method :round
+ alias precisionless_round round
+ private :precisionless_round
# Rounds the float with the specified precision.
#
@@ -12,7 +13,7 @@ class Float
magnitude = 10.0 ** precision
(self * magnitude).round / magnitude
else
- super()
+ precisionless_round
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index ca23d45666..578c025fcf 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -12,7 +12,7 @@ module ActiveSupport
end
def default_behavior
- Deprecation::DEFAULT_BEHAVIORS[defined?(Rails) ? Rails.env.to_s : 'test']
+ Deprecation::DEFAULT_BEHAVIORS[defined?(Rails.env) ? Rails.env.to_s : 'test']
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index c1f0e4bf81..db5afb5324 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -36,7 +36,7 @@ module ActiveSupport
end
def is_a?(klass) #:nodoc:
- klass == Duration || super
+ Duration == klass || value.is_a?(klass)
end
# Returns true if <tt>other</tt> is also a Duration instance with the
@@ -50,7 +50,9 @@ module ActiveSupport
end
def self.===(other) #:nodoc:
- other.is_a?(Duration) rescue super
+ other.is_a?(Duration)
+ rescue ::NoMethodError
+ false
end
# Calculates a new Time or Date that is as far in the future
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index c8415d5449..8ba45f7ea2 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -114,7 +114,8 @@ module ActiveSupport
end
end
- self.escape_html_entities_in_json = true
+ self.use_standard_json_time_format = true
+ self.escape_html_entities_in_json = false
end
CircularReferenceError = Deprecation::DeprecatedConstantProxy.new('ActiveSupport::JSON::CircularReferenceError', Encoding::CircularReferenceError)
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index d9bfcbfcab..3e96decb8c 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -44,11 +44,16 @@ module ActiveSupport
class << self
attr_writer :notifier
- delegate :publish, :subscribe, :instrument, :to => :notifier
+ delegate :publish, :subscribe, :to => :notifier
+ delegate :instrument, :to => :instrumenter
def notifier
@notifier ||= Notifier.new
end
+
+ def instrumenter
+ Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
+ end
end
class Notifier
@@ -67,13 +72,6 @@ module ActiveSupport
def wait
@queue.wait
end
-
- delegate :instrument, :to => :current_instrumenter
-
- private
- def current_instrumenter
- Thread.current[:"instrumentation_#{object_id}"] ||= Notifications::Instrumenter.new(self)
- end
end
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 0655dd0cb6..f3d877efe7 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -4,16 +4,20 @@ require 'active_support/core_ext/module/delegation'
module ActiveSupport
module Notifications
class Instrumenter
+ attr_reader :id
+
def initialize(notifier)
@id = unique_id
@notifier = notifier
end
+ # Instrument the given block by measuring the time taken to execute it
+ # and publish it.
def instrument(name, payload={})
time = Time.now
- yield if block_given?
- ensure
+ result = yield(payload) if block_given?
@notifier.publish(name, time, Time.now, @id, payload)
+ result
end
private
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 6b554e7158..710dce78de 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -32,7 +32,6 @@ module ActiveSupport
# t.is_a?(Time) # => true
# t.is_a?(ActiveSupport::TimeWithZone) # => true
class TimeWithZone
-
def self.name
'Time' # Report class name as 'Time' to thwart type checking
end
@@ -114,9 +113,9 @@ module ActiveSupport
end
alias_method :iso8601, :xmlschema
- # Coerces the date to a string for JSON encoding.
- #
- # ISO 8601 format is used if ActiveSupport::JSON::Encoding.use_standard_json_time_format is set.
+ # Coerces the date to a string for JSON encoding. The default format is ISO 8601. You can get
+ # %Y/%m/%d %H:%M:%S +offset style by setting ActiveSupport::JSON::Encoding.use_standard_json_time_format
+ # to false.
#
# ==== Examples
#
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index dda139372e..d91e0415c4 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -13,7 +13,6 @@ require 'mocha'
ENV['NO_RELOAD'] = '1'
require 'active_support'
-require 'active_support/test_case'
# Include shims until we get off 1.8.6
require 'active_support/ruby/shim'
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 42b4f10172..6530de1ef4 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -2,6 +2,31 @@ require 'abstract_unit'
require 'active_support/time'
class DurationTest < ActiveSupport::TestCase
+ def test_is_a
+ d = 1.day
+ assert d.is_a?(ActiveSupport::Duration)
+ assert d.is_a?(Numeric)
+ assert d.is_a?(Fixnum)
+ assert !d.is_a?(Hash)
+
+ k = Class.new
+ class << k; undef_method :== end
+ assert !d.is_a?(k)
+ end
+
+ def test_threequals
+ assert ActiveSupport::Duration === 1.day
+ assert !(ActiveSupport::Duration === 1.day.to_i)
+ assert !(ActiveSupport::Duration === 'foo')
+ assert !(ActiveSupport::Duration === ActiveSupport::BasicObject.new)
+ end
+
+ def test_equals
+ assert 1.day == 1.day
+ assert 1.day == 1.day.to_i
+ assert !(1.day == 'foo')
+ end
+
def test_inspect
assert_equal '0 seconds', 0.seconds.inspect
assert_equal '1 month', 1.month.inspect
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 3ba77ae135..c3eb1a4eb5 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -5,7 +5,8 @@ module Notifications
def setup
Thread.abort_on_exception = true
- @notifier = ActiveSupport::Notifications::Notifier.new
+ ActiveSupport::Notifications.notifier = nil
+ @notifier = ActiveSupport::Notifications.notifier
@events = []
@notifier.subscribe { |*args| @events << event(*args) }
end
@@ -82,13 +83,29 @@ module Notifications
end
class InstrumentationTest < TestCase
+ delegate :instrument, :instrument!, :to => ActiveSupport::Notifications
+
def test_instrument_returns_block_result
- assert_equal 2, @notifier.instrument(:awesome) { 1 + 1 }
+ assert_equal 2, instrument(:awesome) { 1 + 1 }
+ drain
+ end
+
+ def test_instrument_yields_the_paylod_for_further_modification
+ assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 }
+ drain
+
+ assert_equal 1, @events.size
+ assert_equal :awesome, @events.first.name
+ assert_equal Hash[:result => 2], @events.first.payload
+ end
+
+ def test_instrumenter_exposes_its_id
+ assert_equal 20, ActiveSupport::Notifications.instrumenter.id.size
end
def test_nested_events_can_be_instrumented
- @notifier.instrument(:awesome, :payload => "notifications") do
- @notifier.instrument(:wot, :payload => "child") do
+ instrument(:awesome, :payload => "notifications") do
+ instrument(:wot, :payload => "child") do
1 + 1
end
@@ -106,24 +123,21 @@ module Notifications
assert_equal Hash[:payload => "notifications"], @events.last.payload
end
- def test_instrument_publishes_when_exception_is_raised
+ def test_instrument_does_not_publish_when_exception_is_raised
begin
- @notifier.instrument(:awesome, :payload => "notifications") do
+ instrument(:awesome, :payload => "notifications") do
raise "OMG"
end
- flunk
- rescue
+ rescue RuntimeError => e
+ assert_equal "OMG", e.message
end
drain
-
- assert_equal 1, @events.size
- assert_equal :awesome, @events.last.name
- assert_equal Hash[:payload => "notifications"], @events.last.payload
+ assert_equal 0, @events.size
end
def test_event_is_pushed_even_without_block
- @notifier.instrument(:awesome, :payload => "notifications")
+ instrument(:awesome, :payload => "notifications")
drain
assert_equal 1, @events.size
diff --git a/rack b/rack
deleted file mode 160000
-Subproject c6805fb93da30e0056b38e0fa6015c3d1bca587
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 0bc1ea32bc..fc9277bd28 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* Removed config/initializers/new_rails_defaults.rb as all frameworks now follow the settings from it [DHH]
+
+* Set config.time_zone to UTC by default [DHH]
+
* Added default .gitignore (this is just recognizing Git market share, don't throw a hissy if you use another SCM) [DHH]
* Added cookies.permanent, cookies.signed, and cookies.permanent.signed accessor for common cookie actions [DHH]. Examples:
diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb
index c3784cff32..269fe488b0 100644
--- a/railties/builtin/rails_info/rails/info.rb
+++ b/railties/builtin/rails_info/rails/info.rb
@@ -129,12 +129,12 @@ module Rails
# The current Rails environment (development, test, or production).
property 'Environment' do
- RAILS_ENV
+ Rails.env
end
# The name of the database adapter for the current environment.
property 'Database adapter' do
- ActiveRecord::Base.configurations[RAILS_ENV]['adapter']
+ ActiveRecord::Base.configurations[Rails.env]['adapter']
end
property 'Database schema version' do
diff --git a/railties/builtin/rails_info/rails_info_controller.rb b/railties/builtin/rails_info/rails_info_controller.rb
deleted file mode 100644
index 2009eb3a99..0000000000
--- a/railties/builtin/rails_info/rails_info_controller.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-# Alias to ensure old public.html still works.
-RailsInfoController = Rails::InfoController
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb
index d4796b65c1..bd25111405 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/railties/guides/rails_guides/generator.rb
@@ -101,7 +101,7 @@ module RailsGuides
view.content_tag(:li, l)
end
- children_ul = view.content_tag(:ul, children)
+ children_ul = view.content_tag(:ul, children.join(" "))
index << view.content_tag(:li, link + children_ul)
end
diff --git a/railties/guides/rails_guides/indexer.rb b/railties/guides/rails_guides/indexer.rb
index 5b5ad3fee1..939404c85f 100644
--- a/railties/guides/rails_guides/indexer.rb
+++ b/railties/guides/rails_guides/indexer.rb
@@ -19,9 +19,9 @@ module RailsGuides
level_hash = ActiveSupport::OrderedHash.new
while !s.eos?
- s.match?(/\h[0-9]\..*$/)
+ s.match?(/h[0-9]\..*$/)
if matched = s.matched
- matched =~ /\h([0-9])\.(.*)$/
+ matched =~ /h([0-9])\.(.*)$/
level, title = $1.to_i, $2
if level < current_level
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 3073c3a7a5..f8a72db703 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -323,6 +323,46 @@ TIP: Since +with_options+ forwards calls to its receiver they can be nested. Eac
NOTE: Defined in +active_support/core_ext/object/with_options.rb+.
+h4. Modules and Classes
+
+h5. +remove_subclasses_of+
+
+The method +remove_subclasses_of+ receives an arbitrary number of class objects and removes their subclasses. It is a wrapper of +Class#remove_class+ explained with more details in "Class Removal FIX THIS LINK":FIXME.
+
+h5. +subclasses_of+
+
+The method +subclasses_of+ receives an arbitrary number of class objects and returns all their anonymous or reachable descendants as a single array:
+
+<ruby>
+class C; end
+subclasses_of(C) # => []
+
+subclasses_of(Integer) # => [Bignum, Fixnum]
+
+module M
+ class A; end
+ class B1 < A; end
+ class B2 < A; end
+end
+
+module N
+ class C < M::B1; end
+end
+
+subclasses_of(M::A) # => [N::C, M::B2, M::B1]
+</ruby>
+
+The order in which these classes are returned is unspecified. The returned collection may have duplicates:
+
+<ruby>
+subclasses_of(Numeric, Integer)
+# => [Bignum, Float, Fixnum, Integer, Date::Infinity, Rational, BigDecimal, Bignum, Fixnum]
+</ruby>
+
+See also +Class#subclasses+ in "Extensions to +Class+ FIXME THIS LINK":FIXME.
+
+NOTE: Defined in +active_support/core_ext/object/extending.rb+.
+
h4. Instance Variables
Active Support provides several methods to ease access to instance variables.
@@ -587,9 +627,19 @@ If for whatever reason an application loads the definition of a mailer class and
NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+.
-h4. Subclasses
+h4. Descendants
+
+h5. +descendents+
+
+The +descendents+ method returns all the descendants of its receiver as an array of class objects. This method performs no filtering so non-reachable classes are included, if any.
-The +subclasses+ method returns the names of all subclasses of a given class as an array of strings. That comprises not only direct subclasses, but all descendants down the hierarchy:
+See aso +Object#subclasses_of+, explained in "Extensions to All Objects FIX THIS LINK":FIXME.
+
+NOTE: Defined in +active_support/core_ext/object/extending.rb+.
+
+h5. +subclasses+
+
+The +subclasses+ method returns the names of all the anonymous or reachable descendants of its receiver as an array of strings:
<ruby>
class C; end
@@ -610,9 +660,9 @@ end
M::A.subclasses # => ["N::C", "M::B2", "M::B1"]
</ruby>
-The order in which these class names are returned is unspecified.
+WARNING: +ActiveRecord::Base+ redefines +subclasses+, it returns class objects, reachable or not, and it is protected.
-See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME.
+See aso +Object#subclasses_of+, explained in "Extensions to All Objects FIX THIS LINK":FIXME.
NOTE: Defined in +active_support/core_ext/class/removal.rb+.
@@ -663,6 +713,31 @@ See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LI
NOTE: Defined in +active_support/core_ext/class/removal.rb+.
+h4. Reachable Classes
+
+By definition a non-anonymous class is reachable if its name constantized is defined, and the corresponding constant evaluates to +self+:
+
+<ruby>
+class C; end
+C.reachable? # => true
+
+phantom = Object.send(:remove_const, :C)
+
+# The class object is orphan now but it still has a name.
+phantom.name # => "C"
+
+# Class name no longer available as a constant.
+phantom.reachable? # => nil
+
+# Let's define a class named "C" again.
+class C; end
+
+# Class name available as a constant, but different class object.
+phantom.reachable? # => false
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/class/removal.rb+.
+
h3. Extensions to +String+
h4. +squish+
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index eb66366d07..7dfcf4a507 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -87,7 +87,7 @@
<div id="container">
<div class="wrapper">
<div id="mainCol">
- <%= yield %>
+ <%= yield.html_safe! %>
</div>
</div>
</div>
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index d69e3eea6a..4ded2515fc 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -15,6 +15,7 @@ require 'rails/rack'
require 'rails/paths'
require 'rails/configuration'
require 'rails/deprecation'
+require 'rails/subscriber'
require 'rails/ruby_version_check'
# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
@@ -29,14 +30,9 @@ else
Encoding.default_external = Encoding::UTF_8
end
-RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV)
-
module Rails
- # Needs to be duplicated from Active Support since its needed before Active
- # Support is available. Here both Options and Hash are namespaced to prevent
- # conflicts with other implementations AND with the classes residing in Active Support.
- # ---
- # TODO: w0t?
+ autoload :Bootstrap, 'rails/bootstrap'
+
class << self
def application
@@application ||= nil
@@ -48,7 +44,7 @@ module Rails
# The Configuration instance used to configure the Rails environment
def configuration
- application.configuration
+ application.config
end
def initialize!
@@ -56,19 +52,19 @@ module Rails
end
def initialized?
- @initialized || false
+ @@initialized || false
end
def initialized=(initialized)
- @initialized ||= initialized
+ @@initialized ||= initialized
end
def logger
- if defined?(RAILS_DEFAULT_LOGGER)
- RAILS_DEFAULT_LOGGER
- else
- nil
- end
+ @@logger ||= nil
+ end
+
+ def logger=(logger)
+ @@logger = logger
end
def backtrace_cleaner
@@ -84,7 +80,7 @@ module Rails
end
def env
- @_env ||= ActiveSupport::StringInquirer.new(RAILS_ENV)
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
end
def cache
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 5401251397..c95316a4da 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -83,17 +83,19 @@ module Rails
end
def initializers
- initializers = super
+ initializers = Bootstrap.new(self).initializers
plugins.each { |p| initializers += p.initializers }
+ initializers += super
initializers
end
# TODO: Fix this method
def plugins
@plugins ||= begin
- plugin_names = config.plugins || [:all]
- Railtie.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } +
- Plugin.all(config.plugins || [:all], config.paths.vendor.plugins)
+ plugin_names = (config.plugins || [:all]).map { |p| p.to_sym }
+ Railtie.plugins.select { |p|
+ plugin_names.include?(:all) || plugin_names.include?(p.plugin_name)
+ }.map { |p| p.new } + Plugin.all(plugin_names, config.paths.vendor.plugins)
end
end
@@ -102,140 +104,6 @@ module Rails
@app.call(env)
end
- initializer :load_all_active_support do
- require "active_support/all" unless config.active_support.bare
- end
-
- # Set the <tt>$LOAD_PATH</tt> based on the value of
- # Configuration#load_paths. Duplicates are removed.
- initializer :set_load_path do
- config.paths.add_to_load_path
- $LOAD_PATH.uniq!
- end
-
- # Set the paths from which Rails will automatically load source files, and
- # the load_once paths.
- initializer :set_autoload_paths do
- require 'active_support/dependencies'
- ActiveSupport::Dependencies.load_paths = config.load_paths.uniq
- ActiveSupport::Dependencies.load_once_paths = config.load_once_paths.uniq
-
- extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths
- unless extra.empty?
- abort <<-end_error
- load_once_paths must be a subset of the load_paths.
- Extra items in load_once_paths: #{extra * ','}
- end_error
- end
-
- # Freeze the arrays so future modifications will fail rather than do nothing mysteriously
- config.load_once_paths.freeze
- end
-
- # Create tmp directories
- initializer :ensure_tmp_directories_exist do
- %w(cache pids sessions sockets).each do |dir_to_make|
- FileUtils.mkdir_p(File.join(root, 'tmp', dir_to_make))
- end
- end
-
- # Preload all frameworks specified by the Configuration#frameworks.
- # Used by Passenger to ensure everything's loaded before forking and
- # to avoid autoload race conditions in JRuby.
- initializer :preload_frameworks do
- ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks
- end
-
- initializer :initialize_cache do
- unless defined?(RAILS_CACHE)
- silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) }
-
- if RAILS_CACHE.respond_to?(:middleware)
- # Insert middleware to setup and teardown local cache for each request
- config.middleware.insert_after(:"Rack::Lock", RAILS_CACHE.middleware)
- end
- end
- end
-
- initializer :initialize_logger do
- # if the environment has explicitly defined a logger, use it
- next if Rails.logger
-
- unless logger = config.logger
- begin
- logger = ActiveSupport::BufferedLogger.new(config.log_path)
- logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
- if RAILS_ENV == "production"
- logger.auto_flushing = false
- end
- rescue StandardError => e
- logger = ActiveSupport::BufferedLogger.new(STDERR)
- logger.level = ActiveSupport::BufferedLogger::WARN
- logger.warn(
- "Rails Error: Unable to access log file. Please ensure that #{config.log_path} exists and is chmod 0666. " +
- "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
- )
- end
- end
-
- # TODO: Why are we silencing warning here?
- silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
- end
-
- # Sets the logger for Active Record, Action Controller, and Action Mailer
- # (but only for those frameworks that are to be loaded). If the framework's
- # logger is already set, it is not changed, otherwise it is set to use
- # RAILS_DEFAULT_LOGGER.
- initializer :initialize_framework_logging do
- ActiveSupport::Dependencies.logger ||= Rails.logger
- Rails.cache.logger ||= Rails.logger
- end
-
- # Sets the dependency loading mechanism based on the value of
- # Configuration#cache_classes.
- initializer :initialize_dependency_mechanism do
- # TODO: Remove files from the $" and always use require
- ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
- end
-
- # Loads support for "whiny nil" (noisy warnings when methods are invoked
- # on +nil+ values) if Configuration#whiny_nils is true.
- initializer :initialize_whiny_nils do
- require('active_support/whiny_nil') if config.whiny_nils
- end
-
- # Sets the default value for Time.zone
- # If assigned value cannot be matched to a TimeZone, an exception will be raised.
- initializer :initialize_time_zone do
- if config.time_zone
- require 'active_support/core_ext/time/zones'
- zone_default = Time.__send__(:get_zone, config.time_zone)
-
- unless zone_default
- raise \
- 'Value assigned to config.time_zone not recognized.' +
- 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
- end
-
- Time.zone_default = zone_default
- end
- end
-
- # Set the i18n configuration from config.i18n but special-case for the load_path which should be
- # appended to what's already set instead of overwritten.
- initializer :initialize_i18n do
- config.i18n.each do |setting, value|
- if setting == :load_path
- I18n.load_path += value
- else
- I18n.send("#{setting}=", value)
- end
- end
- end
-
- # # bail out if gems are missing - note that check_gem_dependencies will have
- # # already called abort() unless $gems_rake_task is set
- # return unless gems_dependencies_loaded
initializer :load_application_initializers do
Dir["#{root}/config/initializers/**/*.rb"].sort.each do |initializer|
load(initializer)
diff --git a/railties/lib/rails/bootstrap.rb b/railties/lib/rails/bootstrap.rb
new file mode 100644
index 0000000000..b7cf70747a
--- /dev/null
+++ b/railties/lib/rails/bootstrap.rb
@@ -0,0 +1,156 @@
+module Rails
+ class Bootstrap #< Railtie
+ include Initializable
+
+ def initialize(application)
+ @application = application
+ end
+
+ delegate :config, :root, :to => :'@application'
+
+ initializer :load_all_active_support do
+ require "active_support/all" unless config.active_support.bare
+ end
+
+ # Set the <tt>$LOAD_PATH</tt> based on the value of
+ # Configuration#load_paths. Duplicates are removed.
+ initializer :set_load_path do
+ config.paths.add_to_load_path
+ $LOAD_PATH.uniq!
+ end
+
+ # Set the paths from which Rails will automatically load source files, and
+ # the load_once paths.
+ initializer :set_autoload_paths do
+ require 'active_support/dependencies'
+ ActiveSupport::Dependencies.load_paths = config.load_paths.uniq
+ ActiveSupport::Dependencies.load_once_paths = config.load_once_paths.uniq
+
+ extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths
+ unless extra.empty?
+ abort <<-end_error
+ load_once_paths must be a subset of the load_paths.
+ Extra items in load_once_paths: #{extra * ','}
+ end_error
+ end
+
+ # Freeze the arrays so future modifications will fail rather than do nothing mysteriously
+ config.load_once_paths.freeze
+ end
+
+ # Create tmp directories
+ initializer :ensure_tmp_directories_exist do
+ %w(cache pids sessions sockets).each do |dir_to_make|
+ FileUtils.mkdir_p(File.join(root, 'tmp', dir_to_make))
+ end
+ end
+
+ # Preload all frameworks specified by the Configuration#frameworks.
+ # Used by Passenger to ensure everything's loaded before forking and
+ # to avoid autoload race conditions in JRuby.
+ initializer :preload_frameworks do
+ ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks
+ end
+
+ initializer :initialize_cache do
+ unless defined?(RAILS_CACHE)
+ silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) }
+
+ if RAILS_CACHE.respond_to?(:middleware)
+ # Insert middleware to setup and teardown local cache for each request
+ config.middleware.insert_after(:"Rack::Lock", RAILS_CACHE.middleware)
+ end
+ end
+ end
+
+ initializer :initialize_logger do
+ Rails.logger ||= config.logger || begin
+ logger = ActiveSupport::BufferedLogger.new(config.log_path)
+ logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
+ logger.auto_flushing = false if Rails.env.production?
+ logger
+ rescue StandardError => e
+ logger = ActiveSupport::BufferedLogger.new(STDERR)
+ logger.level = ActiveSupport::BufferedLogger::WARN
+ logger.warn(
+ "Rails Error: Unable to access log file. Please ensure that #{config.log_path} exists and is chmod 0666. " +
+ "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
+ )
+ logger
+ end
+ end
+
+ # Sets the logger for dependencies and cache store.
+ initializer :initialize_framework_logging do
+ ActiveSupport::Dependencies.logger ||= Rails.logger
+ Rails.cache.logger ||= Rails.logger
+ end
+
+ # Sets the dependency loading mechanism based on the value of
+ # Configuration#cache_classes.
+ initializer :initialize_dependency_mechanism do
+ # TODO: Remove files from the $" and always use require
+ ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
+ end
+
+ # Loads support for "whiny nil" (noisy warnings when methods are invoked
+ # on +nil+ values) if Configuration#whiny_nils is true.
+ initializer :initialize_whiny_nils do
+ require 'active_support/whiny_nil' if config.whiny_nils
+ end
+
+ # Sets the default value for Time.zone
+ # If assigned value cannot be matched to a TimeZone, an exception will be raised.
+ initializer :initialize_time_zone do
+ require 'active_support/core_ext/time/zones'
+ zone_default = Time.__send__(:get_zone, config.time_zone)
+
+ unless zone_default
+ raise \
+ 'Value assigned to config.time_zone not recognized.' +
+ 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
+ end
+
+ Time.zone_default = zone_default
+ end
+
+ # Set the i18n configuration from config.i18n but special-case for the load_path which should be
+ # appended to what's already set instead of overwritten.
+ initializer :initialize_i18n do
+ require 'active_support/i18n'
+
+ config.i18n.each do |setting, value|
+ if setting == :load_path
+ I18n.load_path += value
+ else
+ I18n.send("#{setting}=", value)
+ end
+ end
+
+ ActionDispatch::Callbacks.to_prepare do
+ I18n.reload!
+ end
+ end
+
+ initializer :set_clear_dependencies_hook do
+ unless config.cache_classes
+ ActionDispatch::Callbacks.after do
+ ActiveSupport::Dependencies.clear
+ end
+ end
+ end
+
+ initializer :initialize_notifications do
+ require 'active_support/notifications'
+
+ if config.colorize_logging == false
+ Rails::Subscriber.colorize_logging = false
+ config.generators.colorize_logging = false
+ end
+
+ ActiveSupport::Notifications.subscribe do |*args|
+ Rails::Subscriber.dispatch(args)
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 37eb6d40ea..27ac7fd20a 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -4,8 +4,6 @@ require "irb/completion"
module Rails
class Console
- ENVIRONMENTS = %w(production development test)
-
def self.start(app)
new(app).start
end
@@ -25,10 +23,6 @@ module Rails
opt.parse!(ARGV)
end
- if env = ARGV.first
- ENV['RAILS_ENV'] = ENVIRONMENTS.find { |e| e.index(env) } || env
- end
-
@app.initialize!
require "rails/console_app"
require "rails/console_sandbox" if options[:sandbox]
@@ -54,3 +48,8 @@ module Rails
end
end
end
+
+# Has to set the RAILS_ENV before config/application is required
+if ARGV.first && !ARGV.first.index("-") && env = ARGV.pop # has to pop the env ARGV so IRB doesn't freak
+ ENV['RAILS_ENV'] = %w(production development test).find { |e| e.index(env) } || env
+end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 77c3404343..593e2d8ee3 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -34,9 +34,8 @@ module Rails
abort opt.to_s unless (0..1).include?(ARGV.size)
end
- env = ARGV.first || ENV['RAILS_ENV'] || 'development'
- unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[env]
- abort "No database is configured for the environment '#{env}'"
+ unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[Rails.env]
+ abort "No database is configured for the environment '#{Rails.env}'"
end
@@ -97,4 +96,9 @@ module Rails
end
end
end
+end
+
+# Has to set the RAILS_ENV before config/application is required
+if ARGV.first && !ARGV.first.index("-") && env = ARGV.first
+ ENV['RAILS_ENV'] = %w(production development test).find { |e| e.index(env) } || env
end \ No newline at end of file
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
index f85c17bb94..a2eff377ce 100644
--- a/railties/lib/rails/commands/destroy.rb
+++ b/railties/lib/rails/commands/destroy.rb
@@ -7,4 +7,4 @@ if ARGV.size == 0
end
name = ARGV.shift
-Rails::Generators.invoke name, ARGV, :behavior => :revoke
+Rails::Generators.invoke name, ARGV, :behavior => :revoke, :destination_root => Rails.root
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index c5e3ae3529..c1120aad74 100755
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -7,4 +7,4 @@ if ARGV.size == 0
end
name = ARGV.shift
-Rails::Generators.invoke name, ARGV, :behavior => :invoke
+Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rails.root
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 0246348c77..4487d2e7b1 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -34,7 +34,6 @@ end
ARGV.delete(code_or_file)
ENV["RAILS_ENV"] = options[:environment]
-RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
begin
if code_or_file.nil?
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 09d7207d51..115499db05 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -38,15 +38,15 @@ module Rails
end
def start
+ ENV["RAILS_ENV"] = options[:environment]
+
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}"
puts "=> Call with -d to detach" unless options[:daemonize]
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
- ENV["RAILS_ENV"] = options[:environment]
- RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV)
-
+ initialize_log_tailer! unless options[:daemonize]
super
ensure
puts 'Exiting' unless options[:daemonize]
@@ -54,7 +54,6 @@ module Rails
def middleware
middlewares = []
- middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize]
middlewares << [Rails::Rack::Debugger] if options[:debugger]
Hash.new(middlewares)
end
@@ -72,5 +71,14 @@ module Rails
:pid => "tmp/pids/server.pid"
})
end
+
+ protected
+
+ # LogTailer should not be used as a middleware since the logging happens
+ # async in a request and the middleware calls are sync. So we send it
+ # to subscriber which will be responsible for calling tail! in the log tailer.
+ def initialize_log_tailer!
+ Rails::Subscriber.log_tailer = Rails::Rack::LogTailer.new(nil, log_path)
+ end
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index f0a0d5e55e..a2fab120cf 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -4,11 +4,25 @@ module Rails
# Temporarily separate the plugin configuration class from the main
# configuration class while this bit is being cleaned up.
class Railtie::Configuration
-
def self.default
@default ||= new
end
+ def self.default_middleware_stack
+ ActionDispatch::MiddlewareStack.new.tap do |middleware|
+ middleware.use('ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { Rails.application.config.serve_static_assets })
+ middleware.use('::Rack::Lock', :if => lambda { !ActionController::Base.allow_concurrency })
+ middleware.use('::Rack::Runtime')
+ middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local })
+ middleware.use('ActionDispatch::Callbacks', lambda { ActionController::Dispatcher.prepare_each_request })
+ middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options })
+ middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store })
+ middleware.use('ActionDispatch::ParamsParser')
+ middleware.use('::Rack::MethodOverride')
+ middleware.use('::ActionDispatch::Head')
+ end
+ end
+
attr_reader :middleware
def initialize(base = nil)
@@ -17,7 +31,7 @@ module Rails
@middleware = base.middleware.dup
else
@options = Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new }
- @middleware = ActionDispatch::MiddlewareStack.new
+ @middleware = self.class.default_middleware_stack
end
end
@@ -51,17 +65,16 @@ module Rails
end
class Configuration < Railtie::Configuration
- attr_accessor :after_initialize_blocks, :cache_classes,
- :consider_all_requests_local, :dependency_loading, :gems,
+ attr_accessor :after_initialize_blocks, :cache_classes, :colorize_logging,
+ :consider_all_requests_local, :dependency_loading,
:load_once_paths, :logger, :metals, :plugins,
:preload_frameworks, :reload_plugins, :serve_static_assets,
:time_zone, :whiny_nils
attr_writer :cache_store, :controller_paths,
:database_configuration_file, :eager_load_paths,
- :i18n, :load_paths,
- :log_level, :log_path, :paths, :routes_configuration_file,
- :view_path
+ :i18n, :load_paths, :log_level, :log_path, :paths,
+ :routes_configuration_file, :view_path
def initialize(base = nil)
super
@@ -114,14 +127,14 @@ module Rails
paths.tmp.cache "tmp/cache"
paths.config "config"
paths.config.locales "config/locales"
- paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb"
+ paths.config.environments "config/environments", :glob => "#{Rails.env}.rb"
paths
end
end
def frameworks(*args)
- raise "config.frameworks in no longer supported. See the generated" \
- "config/boot.rb for steps on how to limit the frameworks that" \
+ raise "config.frameworks in no longer supported. See the generated " \
+ "config/boot.rb for steps on how to limit the frameworks that " \
"will be loaded"
end
alias frameworks= frameworks
@@ -197,7 +210,7 @@ module Rails
paths = []
# Add the old mock paths only if the directories exists
- paths.concat(Dir["#{root}/test/mocks/#{RAILS_ENV}"]) if File.exists?("#{root}/test/mocks/#{RAILS_ENV}")
+ paths.concat(Dir["#{root}/test/mocks/#{Rails.env}"]) if File.exists?("#{root}/test/mocks/#{Rails.env}")
# Add the app's controller directory
paths.concat(Dir["#{root}/app/controllers/"])
@@ -220,15 +233,19 @@ module Rails
def builtin_directories
# Include builtins only in the development environment.
- (RAILS_ENV == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
+ Rails.env.development? ? Dir["#{RAILTIES_PATH}/builtin/*/"] : []
end
def log_path
- @log_path ||= File.join(root, 'log', "#{RAILS_ENV}.log")
+ @log_path ||= File.join(root, 'log', "#{Rails.env}.log")
end
def log_level
- @log_level ||= RAILS_ENV == 'production' ? :info : :debug
+ @log_level ||= Rails.env.production? ? :info : :debug
+ end
+
+ def time_zone
+ @time_zone ||= "UTC"
end
def i18n
@@ -246,7 +263,7 @@ module Rails
end
def environment_path
- "#{root}/config/environments/#{RAILS_ENV}.rb"
+ "#{root}/config/environments/#{Rails.env}.rb"
end
# Holds generators configuration:
@@ -270,10 +287,14 @@ module Rails
end
end
- # Allows Notifications queue to be modified.
+ # Allow Notifications queue to be modified or add subscriptions:
#
# config.notifications.queue = MyNewQueue.new
#
+ # config.notifications.subscribe /action_dispatch.show_exception/ do |*args|
+ # ExceptionDeliver.deliver_exception(args)
+ # end
+ #
def notifications
ActiveSupport::Notifications
end
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
index 3c5b8bdec7..43f08d13df 100644
--- a/railties/lib/rails/deprecation.rb
+++ b/railties/lib/rails/deprecation.rb
@@ -6,8 +6,8 @@ RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
Rails.root
end
- def replace(val)
- puts OMG
+ def replace(*args)
+ warn(caller, :replace, *args)
end
def warn(callstack, called, args)
@@ -16,10 +16,32 @@ RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
end
end).new
-module Rails
- class Configuration
- def gem(*args)
- ActiveSupport::Deprecation.warn("config.gem has been deprecated in favor of the Gemfile.")
- end
+RAILS_ENV = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
+ def target
+ Rails.env
+ end
+
+ def replace(*args)
+ warn(caller, :replace, *args)
+ end
+
+ def warn(callstack, called, args)
+ msg = "RAILS_ENV is deprecated! Use Rails.env instead."
+ ActiveSupport::Deprecation.warn(msg, callstack)
+ end
+end).new
+
+RAILS_DEFAULT_LOGGER = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do
+ def target
+ Rails.logger
end
-end \ No newline at end of file
+
+ def replace(*args)
+ warn(caller, :replace, *args)
+ end
+
+ def warn(callstack, called, args)
+ msg = "RAILS_DEFAULT_LOGGER is deprecated! Use Rails.logger instead."
+ ActiveSupport::Deprecation.warn(msg, callstack)
+ end
+end).new
diff --git a/railties/lib/rails/dispatcher.rb b/railties/lib/rails/dispatcher.rb
index 7f9a6221d9..5d383eacd1 100644
--- a/railties/lib/rails/dispatcher.rb
+++ b/railties/lib/rails/dispatcher.rb
@@ -20,5 +20,5 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require 'action_controller/dispatch/dispatcher'
+require 'action_controller/deprecated/dispatcher'
Dispatcher = ActionController::Dispatcher
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 5e8c2730fd..26abb46644 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -12,16 +12,6 @@ module Rails
add_runtime_options!
- # Always move to rails source root.
- #
- def initialize(*args) #:nodoc:
- if !invoked?(args) && defined?(Rails.root) && Rails.root
- self.destination_root = Rails.root
- FileUtils.cd(destination_root)
- end
- super
- end
-
# Automatically sets the source root based on the class name.
#
def self.source_root
@@ -268,13 +258,6 @@ module Rails
end
end
- # Check if this generator was invoked from another one by inspecting
- # parameters.
- #
- def invoked?(args)
- args.last.is_a?(Hash) && (args.last.key?(:invocations) || args.last.key?(:destination_root))
- end
-
# Use Rails default banner.
#
def self.banner
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index d02028d983..9c19a09616 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -8,10 +8,6 @@
</div>
<% end -%>
<div class="actions">
- <%% if @<%= singular_name %>.new_record? %>
- <%%= f.submit 'Create' %>
- <%% else %>
- <%%= f.submit 'Update' %>
- <%% end %>
+ <%%= f.submit %>
</div>
<%% end %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb
index 51c4ad0e2e..7aa049fe80 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title><%= controller_class_name %>: <%%= controller.action_name %></title>
<%%= stylesheet_link_tag 'scaffold' %>
</head>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index fc6a3cdee8..d58d245168 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -168,12 +168,14 @@ module Rails::Generators
raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
+ def bundle_if_dev_or_edge
+ run "gem bundle" if dev_or_edge?
+ end
+
protected
attr_accessor :rails_template
def set_default_accessors!
- app_name # Cache app name
-
self.rails_template = case options[:template]
when /^http:\/\//
options[:template]
@@ -193,8 +195,12 @@ module Rails::Generators
@app_name ||= File.basename(destination_root)
end
+ def app_const_base
+ @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize
+ end
+
def app_const
- @app_const ||= "#{app_name.gsub(/\W/, '_').squeeze('_').classify}::Application"
+ @app_const ||= "#{app_const_base}::Application"
end
def valid_app_const?
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 59c6d333e2..7b5c89c3e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -5,7 +5,8 @@ gem "rails", "<%= Rails::VERSION::STRING %>"
## Bundle edge rails:
<%- if options.dev? -%>
-gem "rails", :path => "<%= Rails::Generators::RAILS_DEV_PATH %>"
+directory "<%= Rails::Generators::RAILS_DEV_PATH %>", :glob => "{*/,}*.gemspec"
+gem "rails", "<%= Rails::VERSION::STRING %>"
<%- else -%>
<%= "# " unless options.edge? %>gem "rails", :git => "git://github.com/rails/rails.git"
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb
index 9889b52893..2cdf4eae54 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb
@@ -1,8 +1,4 @@
-# Filters added to this controller apply to all controllers in the application.
-# Likewise, all the methods added will be available for all controllers.
-
class ApplicationController < ActionController::Base
- helper :all
protect_from_forgery
filter_parameter_logging :password
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb
index 22a7940eb2..de6be7945c 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb
@@ -1,3 +1,2 @@
-# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index acb8435446..2ab821e38d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,5 +1,4 @@
-# Require your environment file to bootstrap Rails
-require ::File.expand_path('../config/environment', __FILE__)
+# This file is used by Rack-based servers to start the application.
-# Dispatch the request
+require ::File.expand_path('../config/environment', __FILE__)
run <%= app_const %>.instance
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 4097f766a6..334820826f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,6 +1,6 @@
require File.expand_path('../boot', __FILE__)
-module <%= app_name.classify %>
+module <%= app_const_base %>
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
@@ -17,15 +17,14 @@ module <%= app_name.classify %>
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names.
- config.time_zone = 'UTC'
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
# config.i18n.default_locale = :de
- # Configure generators values. Many other options are available, be sure to
- # check the documentation.
+ # Configure generators values. Many other options are available, be sure to check the documentation.
# config.generators do |g|
# g.orm :active_record
# g.template_engine :erb
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt
index 9f05cd5a31..451dbe1d1c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt
@@ -4,4 +4,4 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-ActionController::Base.cookie_verifier_secret = '<%= app_secret %>';
+ActionController::Base.cookie_verifier_secret = '<%= app_secret %>'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb
deleted file mode 100644
index 8ec3186c84..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_rails_defaults.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# These settings change the behavior of Rails 2 apps and will be defaults
-# for Rails 3. You can remove this initializer when Rails 3 is released.
-
-if defined?(ActiveRecord)
- # Include Active Record class name as root for JSON serialized output.
- ActiveRecord::Base.include_root_in_json = true
-
- # Store the full class name (including module namespace) in STI type column.
- ActiveRecord::Base.store_full_sti_class = true
-end
-
-# Use ISO 8601 format for JSON serialized times and dates.
-ActiveSupport.use_standard_json_time_format = true
-
-# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
-# if you're including raw json in an HTML page.
-ActiveSupport.escape_html_entities_in_json = false \ No newline at end of file
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 4499ab84b6..baff704d3e 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
@@ -5,8 +5,8 @@
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
ActionController::Base.session = {
- :key => '_<%= app_name %>_session',
- :secret => '<%= app_secret %>'
+ :key => '_<%= app_name %>_session',
+ :secret => '<%= app_secret %>'
}
# Use the database for sessions instead of the cookie-based default,
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 88ee108e90..9a48320a5f 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
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 9c3c96670b..83660ab187 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
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 f71c86e652..b80307fc16 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index ff2dfd3193..b153ae392f 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Ruby on Rails: Welcome aboard</title>
<style type="text/css" media="screen">
body {
diff --git a/railties/lib/rails/generators/rails/app/templates/script/console.tt b/railties/lib/rails/generators/rails/app/templates/script/console.tt
index 9ddd4cfe62..daba8ba2f1 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/console.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/console.tt
@@ -1,3 +1,5 @@
-require File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands/console'
+require File.expand_path('../../config/application', __FILE__)
+
Rails::Console.start(<%= app_const %>.instance)
diff --git a/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt b/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt
index 96e0bc191b..a7f114a97f 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt
+++ b/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt
@@ -1,3 +1,5 @@
-require File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands/dbconsole'
+require File.expand_path('../../config/application', __FILE__)
+
Rails::DBConsole.start(<%= app_const %>.instance)
diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner b/railties/lib/rails/generators/rails/app/templates/script/runner
index 7a70828e90..3354ed4a28 100755
--- a/railties/lib/rails/generators/rails/app/templates/script/runner
+++ b/railties/lib/rails/generators/rails/app/templates/script/runner
@@ -1,2 +1,3 @@
-require File.expand_path('../../config/environment', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands/runner'
+require File.expand_path('../../config/environment', __FILE__)
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 a16f587d8b..45b551fc7d 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
@@ -3,31 +3,6 @@ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'rails/test_help'
class ActiveSupport::TestCase
- # Transactional fixtures accelerate your tests by wrapping each test method
- # in a transaction that's rolled back on completion. This ensures that the
- # test database remains unchanged so your fixtures don't have to be reloaded
- # between every test method. Fewer database queries means faster tests.
- #
- # Read Mike Clark's excellent walkthrough at
- # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
- #
- # Every Active Record database supports transactions except MyISAM tables
- # in MySQL. Turn off transactional fixtures in this case; however, if you
- # don't care one way or the other, switching from MyISAM to InnoDB tables
- # is recommended.
- #
- # The only drawback to using transactional fixtures is when you actually
- # need to test transactions. Since your test is bracketed by a transaction,
- # any transactions started in your code will be automatically rolled back.
- self.use_transactional_fixtures = true
-
- # Instantiated fixtures are slow, but give you @david where otherwise you
- # would need people(:david). If you don't want to migrate your existing
- # test cases which use the @david style and don't mind the speed hit (each
- # instantiated fixtures translates to a database query per test method),
- # then set this back to true.
- self.use_instantiated_fixtures = false
-
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 643d7856c5..38a3cbb035 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -1,4 +1,3 @@
-require 'active_support/test_case'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/hash/reverse_merge'
require 'rails/generators'
@@ -76,7 +75,7 @@ module Rails
eval "$#{stream} = StringIO.new"
yield
result = eval("$#{stream}").string
- ensure
+ ensure
eval("$#{stream} = #{stream.upcase}")
end
@@ -137,9 +136,9 @@ module Rails
#
# assert_migration "db/migrate/create_products.rb"
#
- # This method manipulates the given path and tries to find any migration which
+ # 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.
@@ -236,4 +235,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
index 348ec33582..2ca36a1e44 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,5 +1,3 @@
require 'rubygems'
require 'test/unit'
require 'active_support'
-require 'active_support/test_case'
-
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index e4bf4035da..9380aa49b6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -16,7 +16,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= file_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, :<%= file_name %> => { }
+ post :create, :<%= file_name %> => <%= table_name %>(:one).attributes
end
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
@@ -33,7 +33,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
end
test "should update <%= file_name %>" do
- put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => { }
+ put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => <%= table_name %>(:one).attributes
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
end
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index 9cc6b9c35b..a057b8f701 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -53,6 +53,7 @@ module Rails
ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views")
end
+ # TODO Isn't it supposed to be :after => "action_controller.initialize_routing" ?
initializer :add_routing_file, :after => :initialize_routing do |app|
routing_file = "#{path}/config/routes.rb"
if File.exist?(routing_file)
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index 9705f65e52..d487bd0542 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -2,7 +2,6 @@ module Rails
module Rack
autoload :Debugger, "rails/rack/debugger"
autoload :LogTailer, "rails/rack/log_tailer"
- autoload :Metal, "rails/rack/metal"
autoload :Static, "rails/rack/static"
end
end
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
index 077311be3c..3fa45156c3 100644
--- a/railties/lib/rails/rack/log_tailer.rb
+++ b/railties/lib/rails/rack/log_tailer.rb
@@ -13,11 +13,11 @@ module Rails
def call(env)
response = @app.call(env)
- tail_log
+ tail!
response
end
- def tail_log
+ def tail!
@file.seek @cursor
mod = @file.mtime.to_f
diff --git a/railties/lib/rails/rack/metal.rb b/railties/lib/rails/rack/metal.rb
deleted file mode 100644
index 6c0732f732..0000000000
--- a/railties/lib/rails/rack/metal.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'active_support/ordered_hash'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/dependencies'
-
-module Rails
- module Rack
- class Metal
- NotFoundResponse = [404, {}, []].freeze
- NotFound = lambda { NotFoundResponse }
-
- cattr_accessor :metal_paths
- self.metal_paths = ["#{Rails.root}/app/metal"]
- cattr_accessor :requested_metals
-
- cattr_accessor :pass_through_on
- self.pass_through_on = 404
-
- def self.metals
- matcher = /#{Regexp.escape('/app/metal/')}(.*)\.rb\Z/
- metal_glob = metal_paths.map{ |base| "#{base}/**/*.rb" }
- all_metals = {}
-
- metal_glob.each do |glob|
- Dir[glob].sort.map do |file|
- file = file.match(matcher)[1]
- all_metals[file.camelize] = file
- end
- end
-
- load_list = requested_metals || all_metals.keys
-
- load_list.map do |requested_metal|
- if metal = all_metals[requested_metal]
- require_dependency metal
- requested_metal.constantize
- end
- end.compact
- end
-
- def initialize(app)
- @app = app
- @pass_through_on = {}
- [*self.class.pass_through_on].each { |status| @pass_through_on[status] = true }
-
- @metals = ActiveSupport::OrderedHash.new
- self.class.metals.each { |app| @metals[app] = true }
- freeze
- end
-
- def call(env)
- @metals.keys.each do |app|
- result = app.call(env)
- return result unless @pass_through_on.include?(result[0].to_i)
- end
- @app.call(env)
- end
- end
- end
-end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index ff28ade35d..43a0303c5b 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -25,6 +25,10 @@ module Rails
Configuration.default
end
+ def self.subscriber(subscriber)
+ Rails::Subscriber.add(plugin_name, subscriber)
+ end
+
def self.rake_tasks(&blk)
@rake_tasks ||= []
@rake_tasks << blk if blk
diff --git a/railties/lib/rails/subscriber.rb b/railties/lib/rails/subscriber.rb
new file mode 100644
index 0000000000..e8d13babf0
--- /dev/null
+++ b/railties/lib/rails/subscriber.rb
@@ -0,0 +1,109 @@
+require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/notifications'
+
+module Rails
+ # Rails::Subscriber is an object set to consume ActiveSupport::Notifications
+ # on initialization with solely purpose of logging. The subscriber dispatches
+ # notifications to a regirested object based on its given namespace.
+ #
+ # An example would be ActiveRecord subscriber responsible for logging queries:
+ #
+ # module ActiveRecord
+ # class Railtie
+ # class Subscriber < Rails::Subscriber
+ # def sql(event)
+ # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
+ # end
+ # end
+ # end
+ # end
+ #
+ # It's finally registed as:
+ #
+ # Rails::Subscriber.add :active_record, ActiveRecord::Railtie::Subscriber.new
+ #
+ # So whenever a "active_record.sql" notification arrive to Rails::Subscriber,
+ # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
+ # the sql method.
+ #
+ # This is useful because it avoids spanning several subscribers just for logging
+ # purposes(which slows down the main thread). Besides of providing a centralized
+ # facility on top of Rails.logger.
+ #
+ # Subscriber also has some helpers to deal with logging and automatically flushes
+ # all logs when the request finishes (via action_dispatch.callback notification).
+ class Subscriber
+ mattr_accessor :colorize_logging, :log_tailer
+ self.colorize_logging = true
+
+ # Embed in a String to clear all previous ANSI sequences.
+ CLEAR = "\e[0m"
+ BOLD = "\e[1m"
+
+ # Colors
+ BLACK = "\e[30m"
+ RED = "\e[31m"
+ GREEN = "\e[32m"
+ YELLOW = "\e[33m"
+ BLUE = "\e[34m"
+ MAGENTA = "\e[35m"
+ CYAN = "\e[36m"
+ WHITE = "\e[37m"
+
+ def self.add(namespace, subscriber)
+ subscribers[namespace.to_sym] = subscriber
+ end
+
+ def self.subscribers
+ @subscribers ||= {}
+ end
+
+ def self.dispatch(args)
+ namespace, name = args[0].split(".")
+ subscriber = subscribers[namespace.to_sym]
+
+ if subscriber.respond_to?(name) && subscriber.logger
+ subscriber.send(name, ActiveSupport::Notifications::Event.new(*args))
+ end
+
+ if args[0] == "action_dispatch.callback" && !subscribers.empty?
+ flush_all!
+ log_tailer.tail! if log_tailer
+ end
+ end
+
+ # Flush all subscribers' logger.
+ def self.flush_all!
+ loggers = subscribers.values.map(&:logger)
+ loggers.uniq!
+ loggers.each { |l| l.flush if l.respond_to?(:flush) }
+ end
+
+ # By default, we use the Rails.logger for logging.
+ def logger
+ Rails.logger
+ end
+
+ protected
+
+ %w(info debug warn error fatal unknown).each do |level|
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{level}(*args, &block)
+ logger.#{level}(*args, &block)
+ end
+ METHOD
+ end
+
+ # Set color by using a string or one of the defined constants. If a third
+ # option is set to true, it also adds bold to the string. This is based
+ # on Highline implementation and it automatically appends CLEAR to the end
+ # of the returned String.
+ #
+ def color(text, color, bold=false)
+ return text unless colorize_logging
+ color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
+ bold = bold ? BOLD : ""
+ "#{bold}#{color}#{text}#{CLEAR}"
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/subscriber/test_helper.rb b/railties/lib/rails/subscriber/test_helper.rb
new file mode 100644
index 0000000000..1464767ed9
--- /dev/null
+++ b/railties/lib/rails/subscriber/test_helper.rb
@@ -0,0 +1,118 @@
+require 'rails/subscriber'
+require 'active_support/notifications'
+
+module Rails
+ class Subscriber
+ # Provides some helpers to deal with testing subscribers by setting up
+ # notifications. Take for instance ActiveRecord subscriber tests:
+ #
+ # module SubscriberTest
+ # Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new)
+ #
+ # def test_basic_query_logging
+ # Developer.all
+ # wait
+ # assert_equal 1, @logger.logged(:debug).size
+ # assert_match /Developer Load/, @logger.logged(:debug).last
+ # assert_match /SELECT \* FROM "developers"/, @logger.logged(:debug).last
+ # end
+ #
+ # class SyncSubscriberTest < ActiveSupport::TestCase
+ # include Rails::Subscriber::SyncTestHelper
+ # include SubscriberTest
+ # end
+ #
+ # class AsyncSubscriberTest < ActiveSupport::TestCase
+ # include Rails::Subscriber::AsyncTestHelper
+ # include SubscriberTest
+ # end
+ # end
+ #
+ # All you need to do is to ensure that your subscriber is added to Rails::Subscriber,
+ # as in the second line of the code above. The test helpers is reponsible for setting
+ # up the queue, subscriptions and turning colors in logs off.
+ #
+ # The messages are available in the @logger instance, which is a logger with limited
+ # powers (it actually do not send anything to your output), and you can collect them
+ # doing @logger.logged(level), where level is the level used in logging, like info,
+ # debug, warn and so on.
+ #
+ module TestHelper
+ def setup
+ Thread.abort_on_exception = true
+
+ @logger = MockLogger.new
+ @notifier = ActiveSupport::Notifications::Notifier.new(queue)
+
+ Rails::Subscriber.colorize_logging = false
+ @notifier.subscribe { |*args| Rails::Subscriber.dispatch(args) }
+
+ set_logger(@logger)
+ ActiveSupport::Notifications.notifier = @notifier
+ end
+
+ def teardown
+ set_logger(nil)
+ ActiveSupport::Notifications.notifier = nil
+ Thread.abort_on_exception = false
+ end
+
+ class MockLogger
+ attr_reader :flush_count
+
+ def initialize
+ @flush_count = 0
+ @logged = Hash.new { |h,k| h[k] = [] }
+ end
+
+ def method_missing(level, message)
+ @logged[level] << message
+ end
+
+ def logged(level)
+ @logged[level].compact.map { |l| l.to_s.strip }
+ end
+
+ def flush
+ @flush_count += 1
+ end
+ end
+
+ # Wait notifications to be published.
+ def wait
+ @notifier.wait
+ end
+
+ # Overwrite if you use another logger in your subscriber:
+ #
+ # def logger
+ # ActiveRecord::Base.logger = @logger
+ # end
+ #
+ def set_logger(logger)
+ Rails.logger = logger
+ end
+ end
+
+ module SyncTestHelper
+ include TestHelper
+
+ def queue
+ ActiveSupport::Notifications::Fanout.new(true)
+ end
+ end
+
+ module AsyncTestHelper
+ include TestHelper
+
+ def queue
+ ActiveSupport::Notifications::Fanout.new(false)
+ end
+
+ def wait
+ sleep(0.01)
+ super
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake
index 9433b3556a..4696f02a98 100644
--- a/railties/lib/rails/tasks/misc.rake
+++ b/railties/lib/rails/tasks/misc.rake
@@ -1,6 +1,7 @@
task :default => :test
task :rails_env do
+ # TODO Do we really need this?
unless defined? RAILS_ENV
RAILS_ENV = ENV['RAILS_ENV'] ||= 'development'
end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 2601765065..350d0b3961 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -1,29 +1,14 @@
# Make double-sure the RAILS_ENV is set to test,
# so fixtures are loaded to the right database
-silence_warnings { RAILS_ENV = "test" }
-
-require 'rack'
-require 'rack/test'
+exit("Abort testing: Your Rails environment is not running in test mode!") unless Rails.env.test?
require 'test/unit'
require 'active_support/core_ext/kernel/requires'
-# AP is always present
-require 'action_controller/test_case'
-require 'action_view/test_case'
-
-require 'action_mailer/test_case' if defined?(ActionMailer)
-require 'active_model/test_case' if defined?(ActiveModel)
-
if defined?(ActiveRecord)
- require 'active_record/test_case'
- require 'active_record/fixtures'
-
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
self.fixture_path = "#{Rails.root}/test/fixtures/"
- self.use_instantiated_fixtures = false
- self.use_transactional_fixtures = true
end
ActionController::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
diff --git a/railties/lib/rails/vendor/bundler/LICENSE b/railties/lib/rails/vendor/bundler/LICENSE
deleted file mode 100644
index 41decca113..0000000000
--- a/railties/lib/rails/vendor/bundler/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2009 Engine Yard
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/README.markdown b/railties/lib/rails/vendor/bundler/README.markdown
deleted file mode 100644
index 26863e96f2..0000000000
--- a/railties/lib/rails/vendor/bundler/README.markdown
+++ /dev/null
@@ -1,162 +0,0 @@
-## Bundler : A gem to bundle gems
-
- Github: http://github.com/wycats/bundler
- Mailing list: http://groups.google.com/group/ruby-bundler
- IRC: #carlhuda on freenode
-
-## Intro
-
-Bundler is a tool that manages gem dependencies for your ruby application. It
-takes a gem manifest file and is able to fetch, download, and install the gems
-and all child dependencies specified in this manifest. It can manage any update
-to the gem manifest file and update the bundled gems accordingly. It also lets
-you run any ruby code in context of the bundled gem environment.
-
-## Disclaimer
-
-This project is under rapid development. It is usable today, but there will be
-many changes in the near future, including to the Gemfile DSL. We will bump up
-versions with changes though. We greatly appreciate feedback.
-
-## Installation
-
-Bundler has no dependencies. Just clone the git repository and install the gem
-with the following rake task:
-
- rake install
-
-## Usage
-
-Bundler requires a gem manifest file to be created. This should be a file named
-`Gemfile` located in the root directory of your application. After the manifest
-has been created, in your shell, cd into your application's directory and run
-`gem bundle`. This will start the bundling process.
-
-### Manifest file
-
-This is where you specify all of your application's dependencies. By default
-this should be in a file named `Gemfile` located in your application's root
-directory. The following is an example of a potential `Gemfile`. For more
-information, please refer to Bundler::ManifestBuilder.
-
- # Specify a dependency on rails. When the bundler downloads gems,
- # it will download rails as well as all of rails' dependencies (such as
- # activerecord, actionpack, etc...)
- #
- # At least one dependency must be specified
- gem "rails"
-
- # Specify a dependency on rack v.1.0.0. The version is optional. If present,
- # it can be specified the same way as with rubygems' #gem method.
- gem "rack", "1.0.0"
-
- # Specify a dependency rspec, but only activate that gem in the "testing"
- # environment (read more about environments later). :except is also a valid
- # option to specify environment restrictions.
- gem "rspec", :only => :testing
-
- # Add http://gems.github.com as a source that the bundler will use
- # to find gems listed in the manifest. By default,
- # http://gems.rubyforge.org is already added to the list.
- #
- # This is an optional setting.
- source "http://gems.github.com"
-
- # Specify where the bundled gems should be stashed. This directory will
- # be a gem repository where all gems are downloaded to and installed to.
- #
- # This is an optional setting.
- # The default is: vendor/gems
- bundle_path "my/bundled/gems"
-
- # Specify where gem executables should be copied to.
- #
- # This is an optional setting.
- # The default is: bin
- bin_path "my/executables"
-
- # Specify that rubygems should be completely disabled. This means that it
- # will be impossible to require it and that available gems will be
- # limited exclusively to gems that have been bundled.
- #
- # The default is to automatically require rubygems. There is also a
- # `disable_system_gems` option that will limit available rubygems to
- # the ones that have been bundled.
- disable_rubygems
-
-### Running Bundler
-
-Once a manifest file has been created, the only thing that needs to be done
-is to run the `gem bundle` command anywhere in your application. The script
-will load the manifest file, resole all the dependencies, download all
-needed gems, and install them into the specified directory.
-
-Every time an update is made to the manifest file, run `gem bundle` again to
-get the changes installed. This will only check the remote sources if your
-currently installed gems do not satisfy the `Gemfile`. If you want to force
-checking for updates on the remote sources, use the `--update` option.
-
-### Running your application
-
-The easiest way to run your application is to start it with an executable
-copied to the specified bin directory (by default, simply bin). For example,
-if the application in question is a rack app, start it with `bin/rackup`.
-This will automatically set the gem environment correctly.
-
-Another way to run arbitrary ruby code in context of the bundled gems is to
-run it with the `gem exec` command. For example:
-
- gem exec ruby my_ruby_script.rb
-
-Yet another way is to manually require the environment file first. This is
-located in `[bundle_path]/environments/default.rb`. For example:
-
- ruby -r vendor/gems/environment.rb my_ruby_script.rb
-
-### Using Bundler with Rails today
-
-It should be possible to use Bundler with Rails today. Here are the steps
-to follow.
-
-* In your rails app, create a Gemfile and specify the gems that your
- application depends on. Make sure to specify rails as well:
-
- gem "rails", "2.1.2"
- gem "will_paginate"
-
- # Optionally, you can disable system gems all together and only
- # use bundled gems.
- disable_system_gems
-
-* Run `gem bundle`
-
-* You can now use rails if you prepend `gem exec` to every call to `script/*`
- but that isn't fun.
-
-* At the top of `config/boot.rb`, add the following line:
-
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment'))
-
-In theory, this should be enough to get going.
-
-## To require rubygems or not
-
-Ideally, no gem would assume the presence of rubygems at runtime. Rubygems provides
-enough features so that this isn't necessary. However, there are a number of gems
-that require specific rubygem features.
-
-If the `disable_rubygems` option is used, Bundler will stub out the most common
-of these features, but it is possible that things will not go as intended quite
-yet. So, if you are brave, try your code without rubygems at runtime.
-
-## Known Issues
-
-* When a gem points to a git repository, the git repository will be cloned
- every time Bundler does a gem dependency resolve.
-
-## Reporting bugs
-
-Please report all bugs on the github issue tracker for the project located
-at:
-
- http://github.com/wycats/bundler/issues/ \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/Rakefile b/railties/lib/rails/vendor/bundler/Rakefile
deleted file mode 100644
index dc4c3d6d46..0000000000
--- a/railties/lib/rails/vendor/bundler/Rakefile
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'rubygems' unless ENV['NO_RUBYGEMS']
-require 'rubygems/specification'
-require 'date'
-
-spec = Gem::Specification.new do |s|
- s.name = "bundler"
- s.version = "0.5.0.pre"
- s.author = "Yehuda Katz"
- s.email = "wycats@gmail.com"
- s.homepage = "http://github.com/wycats/bundler"
- s.description = s.summary = "An easy way to vendor gem dependencies"
-
- s.platform = Gem::Platform::RUBY
- s.has_rdoc = true
- s.extra_rdoc_files = ["README.markdown", "LICENSE"]
-
- s.required_rubygems_version = ">= 1.3.5"
-
- s.require_path = 'lib'
- s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("lib/**/*")
-end
-
-task :default => :spec
-
-begin
- require 'spec/rake/spectask'
-rescue LoadError
- task(:spec) { $stderr.puts '`gem install rspec` to run specs' }
-else
- desc "Run specs"
- Spec::Rake::SpecTask.new do |t|
- t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/fixtures/**/*_spec.rb']
- t.spec_opts = %w(-fs --color)
- end
-end
-
-begin
- require 'rake/gempackagetask'
-rescue LoadError
- task(:gem) { $stderr.puts '`gem install rake` to package gems' }
-else
- Rake::GemPackageTask.new(spec) do |pkg|
- pkg.gem_spec = spec
- end
-end
-
-desc "install the gem locally"
-task :install => [:package] do
- sh %{gem install pkg/#{spec.name}-#{spec.version}}
-end
-
-desc "create a gemspec file"
-task :make_spec do
- File.open("#{spec.name}.gemspec", "w") do |file|
- file.puts spec.to_ruby
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler.rb b/railties/lib/rails/vendor/bundler/lib/bundler.rb
deleted file mode 100644
index 1ede3517dd..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'pathname'
-require 'logger'
-require 'set'
-require 'erb'
-# Required elements of rubygems
-require "rubygems/remote_fetcher"
-require "rubygems/installer"
-
-require "bundler/gem_bundle"
-require "bundler/source"
-require "bundler/finder"
-require "bundler/gem_ext"
-require "bundler/resolver"
-require "bundler/environment"
-require "bundler/dsl"
-require "bundler/cli"
-require "bundler/repository"
-require "bundler/dependency"
-
-module Bundler
- VERSION = "0.5.0"
-
- class << self
- attr_writer :logger
-
- def logger
- @logger ||= begin
- logger = Logger.new(STDOUT, Logger::INFO)
- logger.formatter = proc {|_,_,_,msg| "#{msg}\n" }
- logger
- end
- end
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/cli.rb b/railties/lib/rails/vendor/bundler/lib/bundler/cli.rb
deleted file mode 100644
index df9181fbc4..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/cli.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require "optparse"
-
-module Bundler
- class CLI
- def self.run(command, options = {})
- new(options).run(command)
- rescue DefaultManifestNotFound => e
- Bundler.logger.error "Could not find a Gemfile to use"
- exit 2
- rescue InvalidEnvironmentName => e
- Bundler.logger.error "Gemfile error: #{e.message}"
- exit
- rescue InvalidRepository => e
- Bundler.logger.error e.message
- exit
- rescue VersionConflict => e
- Bundler.logger.error e.message
- exit
- rescue GemNotFound => e
- Bundler.logger.error e.message
- exit
- end
-
- def initialize(options)
- @options = options
- @manifest = Bundler::Environment.load(@options[:manifest])
- end
-
- def bundle
- @manifest.install(@options[:update])
- end
-
- def exec
- @manifest.setup_environment
- # w0t?
- super(*@options[:args])
- end
-
- def run(command)
- send(command)
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/commands/bundle_command.rb b/railties/lib/rails/vendor/bundler/lib/bundler/commands/bundle_command.rb
deleted file mode 100644
index a1f9590f75..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/commands/bundle_command.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class Gem::Commands::BundleCommand < Gem::Command
-
- def initialize
- super('bundle', 'Create a gem bundle based on your Gemfile', {:manifest => nil, :update => false})
-
- add_option('-m', '--manifest MANIFEST', "Specify the path to the manifest file") do |manifest, options|
- options[:manifest] = manifest
- end
-
- add_option('-u', '--update', "Force a remote check for newer gems") do
- options[:update] = true
- end
- end
-
- def usage
- "#{program_name}"
- end
-
- def description # :nodoc:
- <<-EOF
-Bundle stuff
- EOF
- end
-
- def execute
- # Prevent the bundler from getting required unless it is actually being used
- require 'bundler'
- Bundler::CLI.run(:bundle, options)
- end
-
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/commands/exec_command.rb b/railties/lib/rails/vendor/bundler/lib/bundler/commands/exec_command.rb
deleted file mode 100644
index 228aa60619..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/commands/exec_command.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class Gem::Commands::ExecCommand < Gem::Command
-
- def initialize
- super('exec', 'Run a command in context of a gem bundle', {:manifest => nil})
-
- add_option('-m', '--manifest MANIFEST', "Specify the path to the manifest file") do |manifest, options|
- options[:manifest] = manifest
- end
- end
-
- def usage
- "#{program_name} COMMAND"
- end
-
- def arguments # :nodoc:
- "COMMAND command to run in context of the gem bundle"
- end
-
- def description # :nodoc:
- <<-EOF.gsub(' ', '')
- Run in context of a bundle
- EOF
- end
-
- def execute
- # Prevent the bundler from getting required unless it is actually being used
- require 'bundler'
- Bundler::CLI.run(:exec, options)
- end
-
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/dependency.rb b/railties/lib/rails/vendor/bundler/lib/bundler/dependency.rb
deleted file mode 100644
index b627b58662..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/dependency.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Bundler
- class InvalidEnvironmentName < StandardError; end
-
- class Dependency
- attr_reader :name, :version, :require_as, :only, :except
-
- def initialize(name, options = {}, &block)
- options.each do |k, v|
- options[k.to_s] = v
- end
-
- @name = name
- @version = options["version"] || ">= 0"
- @require_as = Array(options["require_as"] || name)
- @only = options["only"]
- @except = options["except"]
- @block = block
-
- if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
- raise InvalidEnvironmentName, "'rubygems' is not a valid environment name"
- end
- end
-
- def in?(environment)
- environment = environment.to_s
-
- return false unless !@only || @only.include?(environment)
- return false if @except && @except.include?(environment)
- true
- end
-
- def to_s
- to_gem_dependency.to_s
- end
-
- def require(environment)
- return unless in?(environment)
-
- @require_as.each do |file|
- super(file)
- end
-
- @block.call if @block
- end
-
- def to_gem_dependency
- @gem_dep ||= Gem::Dependency.new(name, version)
- end
-
- def ==(o)
- [name, version, require_as, only, except] ==
- [o.name, o.version, o.require_as, o.only, o.except]
- end
-
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/dsl.rb b/railties/lib/rails/vendor/bundler/lib/bundler/dsl.rb
deleted file mode 100644
index d9a86ee1fd..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/dsl.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-module Bundler
- class ManifestFileNotFound < StandardError; end
-
- class Dsl
- def initialize(environment)
- @environment = environment
- @sources = Hash.new { |h,k| h[k] = {} }
- end
-
- def bundle_path(path)
- path = Pathname.new(path)
- @environment.gem_path = (path.relative? ?
- @environment.root.join(path) : path).expand_path
- end
-
- def bin_path(path)
- path = Pathname.new(path)
- @environment.bindir = (path.relative? ?
- @environment.root.join(path) : path).expand_path
- end
-
- def disable_rubygems
- @environment.rubygems = false
- end
-
- def disable_system_gems
- @environment.system_gems = false
- end
-
- def source(source)
- source = GemSource.new(:uri => source)
- unless @environment.sources.include?(source)
- @environment.add_source(source)
- end
- end
-
- def only(env)
- old, @only = @only, _combine_onlys(env)
- yield
- @only = old
- end
-
- def except(env)
- old, @except = @except, _combine_excepts(env)
- yield
- @except = old
- end
-
- def clear_sources
- @environment.clear_sources
- end
-
- def gem(name, *args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- version = args.last
-
- options[:only] = _combine_onlys(options[:only] || options["only"])
- options[:except] = _combine_excepts(options[:except] || options["except"])
-
- dep = Dependency.new(name, options.merge(:version => version))
-
- # OMG REFACTORZ. KTHX
- if vendored_at = options[:vendored_at]
- vendored_at = Pathname.new(vendored_at)
- vendored_at = @environment.filename.dirname.join(vendored_at) if vendored_at.relative?
-
- @sources[:directory][vendored_at.to_s] ||= begin
- source = DirectorySource.new(
- :name => name,
- :version => version,
- :location => vendored_at
- )
- @environment.add_priority_source(source)
- true
- end
- elsif git = options[:git]
- @sources[:git][git] ||= begin
- source = GitSource.new(
- :name => name,
- :version => version,
- :uri => git,
- :ref => options[:commit] || options[:tag],
- :branch => options[:branch]
- )
- @environment.add_priority_source(source)
- true
- end
- end
-
- @environment.dependencies << dep
- end
-
- private
-
- def _combine_onlys(only)
- return @only unless only
- only = [only].flatten.compact.uniq.map { |o| o.to_s }
- only &= @only if @only
- only
- end
-
- def _combine_excepts(except)
- return @except unless except
- except = [except].flatten.compact.uniq.map { |o| o.to_s }
- except |= @except if @except
- except
- end
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/environment.rb b/railties/lib/rails/vendor/bundler/lib/bundler/environment.rb
deleted file mode 100644
index f07a9e2c6f..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/environment.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require "rubygems/source_index"
-
-module Bundler
- class DefaultManifestNotFound < StandardError; end
-
- class Environment
- attr_reader :filename, :dependencies
- attr_accessor :rubygems, :system_gems, :gem_path, :bindir
-
- def self.load(gemfile = nil)
- gemfile = gemfile ? Pathname.new(gemfile) : default_manifest_file
-
- unless gemfile.file?
- raise ManifestFileNotFound, "#{filename.inspect} does not exist"
- end
-
- new(gemfile)
- end
-
- def self.default_manifest_file
- current = Pathname.new(Dir.pwd)
-
- until current.root?
- filename = current.join("Gemfile")
- return filename if filename.exist?
- current = current.parent
- end
-
- raise DefaultManifestNotFound
- end
-
- def initialize(filename) #, sources, dependencies, bindir, path, rubygems, system_gems)
- @filename = filename
- @default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org")]
- @sources = []
- @priority_sources = []
- @dependencies = []
- @rubygems = true
- @system_gems = true
-
- # Evaluate the Gemfile
- builder = Dsl.new(self)
- builder.instance_eval(File.read(filename))
- end
-
- def install(update = false)
- begin
- tmp_path = filename.dirname.join(".tmp")
- FileUtils.mkdir_p(tmp_path)
- sources.each { |s| s.tmp_path = tmp_path }
- repository.install(gem_dependencies, sources,
- :rubygems => rubygems,
- :system_gems => system_gems,
- :manifest => filename,
- :update => update
- )
- ensure
- FileUtils.rm_rf(tmp_path)
- end
- Bundler.logger.info "Done."
- end
-
- def setup_environment
- unless system_gems
- ENV["GEM_HOME"] = gem_path
- ENV["GEM_PATH"] = gem_path
- end
- ENV["PATH"] = "#{bindir}:#{ENV["PATH"]}"
- ENV["RUBYOPT"] = "-r#{gem_path}/environment #{ENV["RUBYOPT"]}"
- end
-
- def root
- filename.parent
- end
-
- def gem_path
- @gem_path ||= root.join("vendor", "gems")
- end
-
- def bindir
- @bindir ||= root.join("bin")
- end
-
- def sources
- @priority_sources + @sources + @default_sources
- end
-
- def add_source(source)
- @sources << source
- end
-
- def add_priority_source(source)
- @priority_sources << source
- end
-
- def clear_sources
- @sources.clear
- @default_sources.clear
- end
-
- private
-
- def repository
- @repository ||= Repository.new(gem_path, bindir)
- end
-
- def gem_dependencies
- @gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
- end
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/finder.rb b/railties/lib/rails/vendor/bundler/lib/bundler/finder.rb
deleted file mode 100644
index b77ca65709..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/finder.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-module Bundler
- # Finder behaves like a rubygems source index in that it responds
- # to #search. It also resolves a list of dependencies finding the
- # best possible configuration of gems that satisifes all requirements
- # without causing any gem activation errors.
- class Finder
-
- # Takes an array of gem sources and fetches the full index of
- # gems from each one. It then combines the indexes together keeping
- # track of the original source so that any resolved gem can be
- # fetched from the correct source.
- #
- # ==== Parameters
- # *sources<String>:: URI pointing to the gem repository
- def initialize(*sources)
- @cache = {}
- @index = {}
- @sources = sources
- end
-
- # Searches for a gem that matches the dependency
- #
- # ==== Parameters
- # dependency<Gem::Dependency>:: The gem dependency to search for
- #
- # ==== Returns
- # [Gem::Specification]:: A collection of gem specifications
- # matching the search
- def search(dependency)
- @cache[dependency.hash] ||= begin
- find_by_name(dependency.name).select do |spec|
- dependency =~ spec
- end.sort_by {|s| s.version }
- end
- end
-
- private
-
- def find_by_name(name)
- matches = @index[name] ||= begin
- versions = {}
- @sources.reverse_each do |source|
- versions.merge! source.specs[name] || {}
- end
- versions
- end
- matches.values
- end
-
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/gem_bundle.rb b/railties/lib/rails/vendor/bundler/lib/bundler/gem_bundle.rb
deleted file mode 100644
index 80d7710683..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/gem_bundle.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Bundler
- class GemBundle < Array
- def download(repository)
- sort_by {|s| s.full_name.downcase }.each do |spec|
- spec.source.download(spec, repository)
- end
-
- self
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/gem_ext.rb b/railties/lib/rails/vendor/bundler/lib/bundler/gem_ext.rb
deleted file mode 100644
index 155ad04c7e..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/gem_ext.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Gem
- class Installer
- def app_script_text(bin_file_name)
- path = @gem_home
- template = File.read(File.join(File.dirname(__FILE__), "templates", "app_script.erb"))
- erb = ERB.new(template, nil, '-')
- erb.result(binding)
- end
- end
-
- class Specification
- attr_accessor :source
- attr_accessor :location
-
- # Hack to fix github's strange marshal file
- def specification_version
- @specification_version && @specification_version.to_i
- end
-
- alias full_gem_path_without_location full_gem_path
- def full_gem_path
- @location ? @location : full_gem_path_without_location
- end
- end
-end
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/repository.rb b/railties/lib/rails/vendor/bundler/lib/bundler/repository.rb
deleted file mode 100644
index 1a1dc7497d..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/repository.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require "bundler/repository/gem_repository"
-require "bundler/repository/directory_repository"
-
-module Bundler
- class InvalidRepository < StandardError ; end
-
- class Repository
- attr_reader :path
-
- def initialize(path, bindir)
- FileUtils.mkdir_p(path)
-
- @path = Pathname.new(path)
- @bindir = Pathname.new(bindir)
-
- @repos = {
- :gem => Gems.new(@path, @bindir),
- :directory => Directory.new(@path.join("dirs"), @bindir)
- }
- end
-
- def install(dependencies, sources, options = {})
- if options[:update] || !satisfies?(dependencies)
- fetch(dependencies, sources)
- expand(options)
- else
- # Remove any gems that are still around if the Gemfile changed without
- # requiring new gems to be download (e.g. a line in the Gemfile was
- # removed)
- cleanup(Resolver.resolve(dependencies, [source_index]))
- end
- configure(options)
- sync
- end
-
- def gems
- gems = []
- each_repo do |repo|
- gems.concat repo.gems
- end
- gems
- end
-
- def satisfies?(dependencies)
- index = source_index
- dependencies.all? { |dep| index.search(dep).size > 0 }
- end
-
- def source_index
- index = Gem::SourceIndex.new
-
- each_repo do |repo|
- index.gems.merge!(repo.source_index.gems)
- end
-
- index
- end
-
- def add_spec(type, spec)
- @repos[type].add_spec(spec)
- end
-
- def download_path_for(type)
- @repos[type].download_path_for
- end
-
- private
-
- def cleanup(bundle)
- each_repo do |repo|
- repo.cleanup(bundle)
- end
- end
-
- def each_repo
- @repos.each do |k, repo|
- yield repo
- end
- end
-
- def fetch(dependencies, sources)
- bundle = Resolver.resolve(dependencies, sources)
- # Cleanup here to remove any gems that could cause problem in the expansion
- # phase
- #
- # TODO: Try to avoid double cleanup
- cleanup(bundle)
- bundle.download(self)
- end
-
- def sync
- glob = gems.map { |g| g.executables }.flatten.join(',')
-
- (Dir[@bindir.join("*")] - Dir[@bindir.join("{#{glob}}")]).each do |file|
- Bundler.logger.info "Deleting bin file: #{File.basename(file)}"
- FileUtils.rm_rf(file)
- end
- end
-
- def expand(options)
- each_repo do |repo|
- repo.expand(options)
- end
- end
-
- def configure(options)
- generate_environment(options)
- end
-
- def generate_environment(options)
- FileUtils.mkdir_p(path)
-
- specs = gems
- load_paths = load_paths_for_specs(specs)
- bindir = @bindir.relative_path_from(path).to_s
- filename = options[:manifest].relative_path_from(path).to_s
- spec_files = specs.inject({}) do |hash, spec|
- relative = spec.loaded_from.relative_path_from(@path).to_s
- hash.merge!(spec.name => relative)
- end
-
- File.open(path.join("environment.rb"), "w") do |file|
- template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
- erb = ERB.new(template, nil, '-')
- file.puts erb.result(binding)
- end
- end
-
- def load_paths_for_specs(specs)
- load_paths = []
- specs.each do |spec|
- gem_path = Pathname.new(spec.full_gem_path)
- if spec.bindir
- load_paths << gem_path.join(spec.bindir).relative_path_from(@path).to_s
- end
- spec.require_paths.each do |path|
- load_paths << gem_path.join(path).relative_path_from(@path).to_s
- end
- end
- load_paths
- end
-
- def require_code(file, dep)
- constraint = case
- when dep.only then %{ if #{dep.only.inspect}.include?(env)}
- when dep.except then %{ unless #{dep.except.inspect}.include?(env)}
- end
- "require #{file.inspect}#{constraint}"
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/repository/directory_repository.rb b/railties/lib/rails/vendor/bundler/lib/bundler/repository/directory_repository.rb
deleted file mode 100644
index e97dd38dd5..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/repository/directory_repository.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Bundler
- class Repository
- class Directory
- attr_reader :path, :bindir
-
- def initialize(path, bindir)
- @path = path
- @bindir = bindir
-
- FileUtils.mkdir_p(path.to_s)
- end
-
- def source_index
- index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
- index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
- index
- end
-
- def gems
- source_index.gems.values
- end
-
- def add_spec(spec)
- destination = path.join('specifications')
- destination.mkdir unless destination.exist?
-
- File.open(destination.join("#{spec.full_name}.gemspec"), 'w') do |f|
- f.puts spec.to_ruby
- end
- end
-
- def download_path_for
- @path.join("dirs")
- end
-
- # Checks whether a gem is installed
- def expand(options)
- # raise NotImplementedError
- end
-
- def cleanup(gems)
- # raise NotImplementedError
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/repository/gem_repository.rb b/railties/lib/rails/vendor/bundler/lib/bundler/repository/gem_repository.rb
deleted file mode 100644
index 90de49d83d..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/repository/gem_repository.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-module Bundler
- class Repository
- class Gems
- attr_reader :path, :bindir
-
- def initialize(path, bindir)
- @path = path
- @bindir = bindir
- end
-
- # Returns the source index for all gems installed in the
- # repository
- def source_index
- index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
- index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
- index
- end
-
- def gems
- source_index.gems.values
- end
-
- # Checks whether a gem is installed
- def expand(options)
- cached_gems.each do |name, version|
- unless installed?(name, version)
- install_cached_gem(name, version, options)
- end
- end
- end
-
- def cleanup(gems)
- glob = gems.map { |g| g.full_name }.join(',')
- base = path.join("{cache,specifications,gems}")
-
- (Dir[base.join("*")] - Dir[base.join("{#{glob}}{.gemspec,.gem,}")]).each do |file|
- if File.basename(file) =~ /\.gem$/
- name = File.basename(file, '.gem')
- Bundler.logger.info "Deleting gem: #{name}"
- end
- FileUtils.rm_rf(file)
- end
- end
-
- def add_spec(spec)
- raise NotImplementedError
- end
-
- def download_path_for
- path
- end
-
- private
-
- def cache_path
- @path.join("cache")
- end
-
- def cache_files
- Dir[cache_path.join("*.gem")]
- end
-
- def cached_gems
- cache_files.map do |f|
- full_name = File.basename(f).gsub(/\.gem$/, '')
- full_name.split(/-(?=[^-]+$)/)
- end
- end
-
- def spec_path
- @path.join("specifications")
- end
-
- def spec_files
- Dir[spec_path.join("*.gemspec")]
- end
-
- def gem_path
- @path.join("gems")
- end
-
- def gem_paths
- Dir[gem_path.join("*")]
- end
-
- def installed?(name, version)
- spec_files.any? { |g| File.basename(g) == "#{name}-#{version}.gemspec" } &&
- gem_paths.any? { |g| File.basename(g) == "#{name}-#{version}" }
- end
-
- def install_cached_gem(name, version, options = {})
- cached_gem = cache_path.join("#{name}-#{version}.gem")
- # TODO: Add a warning if cached_gem is not a file
- if cached_gem.file?
- Bundler.logger.info "Installing #{name}-#{version}.gem"
- installer = Gem::Installer.new(cached_gem.to_s, options.merge(
- :install_dir => @path,
- :ignore_dependencies => true,
- :env_shebang => true,
- :wrappers => true,
- :bin_dir => @bindir
- ))
- installer.install
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/resolver.rb b/railties/lib/rails/vendor/bundler/lib/bundler/resolver.rb
deleted file mode 100644
index 2a6a6371c2..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/resolver.rb
+++ /dev/null
@@ -1,189 +0,0 @@
-# This is the latest iteration of the gem dependency resolving algorithm. As of now,
-# it can resolve (as a success of failure) any set of gem dependencies we throw at it
-# in a reasonable amount of time. The most iterations I've seen it take is about 150.
-# The actual implementation of the algorithm is not as good as it could be yet, but that
-# can come later.
-
-# Extending Gem classes to add necessary tracking information
-module Gem
- class Dependency
- def required_by
- @required_by ||= []
- end
- end
- class Specification
- def required_by
- @required_by ||= []
- end
- end
-end
-
-module Bundler
- class GemNotFound < StandardError; end
- class VersionConflict < StandardError; end
-
- class Resolver
-
- attr_reader :errors
-
- # Figures out the best possible configuration of gems that satisfies
- # the list of passed dependencies and any child dependencies without
- # causing any gem activation errors.
- #
- # ==== Parameters
- # *dependencies<Gem::Dependency>:: The list of dependencies to resolve
- #
- # ==== Returns
- # <GemBundle>,nil:: If the list of dependencies can be resolved, a
- # collection of gemspecs is returned. Otherwise, nil is returned.
- def self.resolve(requirements, sources)
- Bundler.logger.info "Calculating dependencies..."
-
- resolver = new(sources)
- result = catch(:success) do
- resolver.resolve(requirements, {})
- output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
- o << " Conflict on: #{conflict.inspect}:\n"
- o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
- o << " * #{requirement} required by #{requirement.required_by.first}\n"
- o << " All possible versions of origin requirements conflict."
- end
- raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
- nil
- end
- result && GemBundle.new(result.values)
- end
-
- def initialize(sources)
- @errors = {}
- @stack = []
- @specs = Hash.new { |h,k| h[k] = {} }
- @cache = {}
- @index = {}
-
- sources.reverse_each do |source|
- source.gems.values.each do |spec|
- # TMP HAX FOR OPTZ
- spec.source = source
- next unless Gem::Platform.match(spec.platform)
- @specs[spec.name][spec.version] = spec
- end
- end
- end
-
- def resolve(reqs, activated)
- # If the requirements are empty, then we are in a success state. Aka, all
- # gem dependencies have been resolved.
- throw :success, activated if reqs.empty?
-
- # Sort requirements so that the ones that are easiest to resolve are first.
- # Easiest to resolve is defined by: Is this gem already activated? Otherwise,
- # check the number of child dependencies this requirement has.
- reqs = reqs.sort_by do |req|
- activated[req.name] ? 0 : search(req).size
- end
-
- activated = activated.dup
- # Pull off the first requirement so that we can resolve it
- current = reqs.shift
-
- # Check if the gem has already been activated, if it has, we will make sure
- # that the currently activated gem satisfies the requirement.
- if existing = activated[current.name]
- if current.version_requirements.satisfied_by?(existing.version)
- @errors.delete(existing.name)
- # Since the current requirement is satisfied, we can continue resolving
- # the remaining requirements.
- resolve(reqs, activated)
- else
- @errors[existing.name] = [existing, current]
- # Since the current requirement conflicts with an activated gem, we need
- # to backtrack to the current requirement's parent and try another version
- # of it (maybe the current requirement won't be present anymore). If the
- # current requirement is a root level requirement, we need to jump back to
- # where the conflicting gem was activated.
- parent = current.required_by.last || existing.required_by.last
- # We track the spot where the current gem was activated because we need
- # to keep a list of every spot a failure happened.
- throw parent.name, existing.required_by.last.name
- end
- else
- # There are no activated gems for the current requirement, so we are going
- # to find all gems that match the current requirement and try them in decending
- # order. We also need to keep a set of all conflicts that happen while trying
- # this gem. This is so that if no versions work, we can figure out the best
- # place to backtrack to.
- conflicts = Set.new
-
- # Fetch all gem versions matching the requirement
- #
- # TODO: Warn / error when no matching versions are found.
- matching_versions = search(current)
-
- if matching_versions.empty?
- if current.required_by.empty?
- raise GemNotFound, "Could not find gem '#{current}' in any of the sources"
- end
- Bundler.logger.warn "Could not find gem '#{current}' (required by '#{current.required_by.last}') in any of the sources"
- end
-
- matching_versions.reverse_each do |spec|
- conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
- conflicts << conflict if conflict
- end
- # If the current requirement is a root level gem and we have conflicts, we
- # can figure out the best spot to backtrack to.
- if current.required_by.empty? && !conflicts.empty?
- # Check the current "catch" stack for the first one that is included in the
- # conflicts set. That is where the parent of the conflicting gem was required.
- # By jumping back to this spot, we can try other version of the parent of
- # the conflicting gem, hopefully finding a combination that activates correctly.
- @stack.reverse_each do |savepoint|
- if conflicts.include?(savepoint)
- throw savepoint
- end
- end
- end
- end
- end
-
- def resolve_requirement(spec, requirement, reqs, activated)
- # We are going to try activating the spec. We need to keep track of stack of
- # requirements that got us to the point of activating this gem.
- spec.required_by.replace requirement.required_by
- spec.required_by << requirement
-
- activated[spec.name] = spec
-
- # Now, we have to loop through all child dependencies and add them to our
- # array of requirements.
- spec.dependencies.each do |dep|
- next if dep.type == :development
- dep.required_by << requirement
- reqs << dep
- end
-
- # We create a savepoint and mark it by the name of the requirement that caused
- # the gem to be activated. If the activated gem ever conflicts, we are able to
- # jump back to this point and try another version of the gem.
- length = @stack.length
- @stack << requirement.name
- retval = catch(requirement.name) do
- resolve(reqs, activated)
- end
- # Since we're doing a lot of throw / catches. A push does not necessarily match
- # up to a pop. So, we simply slice the stack back to what it was before the catch
- # block.
- @stack.slice!(length..-1)
- retval
- end
-
- def search(dependency)
- @cache[dependency.hash] ||= begin
- @specs[dependency.name].values.select do |spec|
- dependency =~ spec
- end.sort_by {|s| s.version }
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/runtime.rb b/railties/lib/rails/vendor/bundler/lib/bundler/runtime.rb
deleted file mode 100644
index 27e0254966..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/runtime.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require File.join(File.dirname(__FILE__), "runtime", "dsl")
-require File.join(File.dirname(__FILE__), "runtime", "dependency") \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/source.rb b/railties/lib/rails/vendor/bundler/lib/bundler/source.rb
deleted file mode 100644
index 37828ca316..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/source.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-module Bundler
- # Represents a source of rubygems. Initially, this is only gem repositories, but
- # eventually, this will be git, svn, HTTP
- class Source
- attr_accessor :tmp_path
- end
-
- class GemSource < Source
- attr_reader :uri
-
- def initialize(options)
- @uri = options[:uri]
- @uri = URI.parse(@uri) unless @uri.is_a?(URI)
- raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
- end
-
- def gems
- @specs ||= fetch_specs
- end
-
- def ==(other)
- uri == other.uri
- end
-
- def to_s
- @uri.to_s
- end
-
- class RubygemsRetardation < StandardError; end
-
- def download(spec, repository)
- Bundler.logger.info "Downloading #{spec.full_name}.gem"
-
- destination = repository.download_path_for(:gem)
-
- unless destination.writable?
- raise RubygemsRetardation
- end
-
- Gem::RemoteFetcher.fetcher.download(spec, uri, repository.download_path_for(:gem))
- end
-
- private
-
- def fetch_specs
- Bundler.logger.info "Updating source: #{to_s}"
-
- deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/Marshal.4.8.Z")
- inflated = Gem.inflate deflated
-
- index = Marshal.load(inflated)
- index.gems
- rescue Gem::RemoteFetcher::FetchError => e
- raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
- end
- end
-
- class DirectorySource < Source
- def initialize(options)
- @name = options[:name]
- @version = options[:version]
- @location = options[:location]
- @require_paths = options[:require_paths] || %w(lib)
- end
-
- def gems
- @gems ||= begin
- specs = {}
-
- # Find any gemspec files in the directory and load those specs
- Dir[@location.join('**', '*.gemspec')].each do |file|
- path = Pathname.new(file).relative_path_from(@location).dirname
- spec = eval(File.read(file))
- spec.require_paths.map! { |p| path.join(p) }
- specs[spec.full_name] = spec
- end
-
- # If a gemspec for the dependency was not found, add it to the list
- if specs.keys.grep(/^#{Regexp.escape(@name)}/).empty?
- case
- when @version.nil?
- raise ArgumentError, "If you use :at, you must specify the gem" \
- "and version you wish to stand in for"
- when !Gem::Version.correct?(@version)
- raise ArgumentError, "If you use :at, you must specify a gem and" \
- "version. You specified #{@version} for the version"
- end
-
- default = Gem::Specification.new do |s|
- s.name = @name
- s.version = Gem::Version.new(@version) if @version
- end
- specs[default.full_name] = default
- end
-
- specs
- end
- end
-
- def ==(other)
- # TMP HAX
- other.is_a?(DirectorySource)
- end
-
- def to_s
- "#{@name} (#{@version}) Located at: '#{@location}'"
- end
-
- def download(spec, repository)
- spec.require_paths.map! { |p| File.join(@location, p) }
- repository.add_spec(:directory, spec)
- end
- end
-
- class GitSource < DirectorySource
- def initialize(options)
- super
- @uri = options[:uri]
- @ref = options[:ref]
- @branch = options[:branch]
- end
-
- def gems
- FileUtils.mkdir_p(tmp_path.join("gitz"))
-
- # TMP HAX to get the *.gemspec reading to work
- @location = tmp_path.join("gitz", @name)
-
- Bundler.logger.info "Cloning git repository at: #{@uri}"
- `git clone #{@uri} #{@location} --no-hardlinks`
-
- if @ref
- Dir.chdir(@location) { `git checkout #{@ref}` }
- elsif @branch && @branch != "master"
- Dir.chdir(@location) { `git checkout origin/#{@branch}` }
- end
- super
- end
-
- def download(spec, repository)
- dest = repository.download_path_for(:directory).join(@name)
- spec.require_paths.map! { |p| File.join(dest, p) }
- repository.add_spec(:directory, spec)
- if spec.name == @name
- FileUtils.mkdir_p(dest.dirname)
- FileUtils.mv(tmp_path.join("gitz", spec.name), dest)
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/templates/app_script.erb b/railties/lib/rails/vendor/bundler/lib/bundler/templates/app_script.erb
deleted file mode 100644
index 3e47a53ca8..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/templates/app_script.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<%= shebang bin_file_name %>
-require File.join(File.dirname(__FILE__), "<%= path.join("environment").relative_path_from(Pathname.new(bin_dir)) %>")
-load File.join(File.dirname(__FILE__), "<%= path.join("gems", @spec.full_name, @spec.bindir, bin_file_name).relative_path_from(Pathname.new(bin_dir)) %>") \ No newline at end of file
diff --git a/railties/lib/rails/vendor/bundler/lib/bundler/templates/environment.erb b/railties/lib/rails/vendor/bundler/lib/bundler/templates/environment.erb
deleted file mode 100644
index 21f3de8854..0000000000
--- a/railties/lib/rails/vendor/bundler/lib/bundler/templates/environment.erb
+++ /dev/null
@@ -1,127 +0,0 @@
-# DO NOT MODIFY THIS FILE
-module Bundler
- dir = File.dirname(__FILE__)
-
-<% unless options[:system_gems] -%>
- ENV["GEM_HOME"] = dir
- ENV["GEM_PATH"] = dir
-<% end -%>
- ENV["PATH"] = "#{dir}/<%= bindir %>:#{ENV["PATH"]}"
- ENV["RUBYOPT"] = "-r#{__FILE__} #{ENV["RUBYOPT"]}"
-
-<% load_paths.each do |load_path| -%>
- $LOAD_PATH.unshift File.expand_path("#{dir}/<%= load_path %>")
-<% end -%>
-
- @gemfile = "#{dir}/<%= filename %>"
-
-<% if options[:rubygems] -%>
- require "rubygems"
-
- @bundled_specs = {}
-<% spec_files.each do |name, path| -%>
- @bundled_specs["<%= name %>"] = eval(File.read("#{dir}/<%= path %>"))
- @bundled_specs["<%= name %>"].loaded_from = "#{dir}/<%= path %>"
-<% end -%>
-
- def self.add_specs_to_loaded_specs
- Gem.loaded_specs.merge! @bundled_specs
- end
-
- def self.add_specs_to_index
- @bundled_specs.each do |name, spec|
- Gem.source_index.add_spec spec
- end
- end
-
- add_specs_to_loaded_specs
- add_specs_to_index
-<% end -%>
-
- def self.require_env(env = nil)
- context = Class.new do
- def initialize(env) @env = env && env.to_s ; end
- def method_missing(*) ; end
- def only(env)
- old, @only = @only, _combine_onlys(env)
- yield
- @only = old
- end
- def except(env)
- old, @except = @except, _combine_excepts(env)
- yield
- @except = old
- end
- def gem(name, *args)
- opt = args.last || {}
- only = _combine_onlys(opt[:only] || opt["only"])
- except = _combine_excepts(opt[:except] || opt["except"])
- files = opt[:require_as] || opt["require_as"] || name
-
- return unless !only || only.any? {|e| e == @env }
- return if except && except.any? {|e| e == @env }
-
- files.each { |f| require f }
- yield if block_given?
- true
- end
- private
- def _combine_onlys(only)
- return @only unless only
- only = [only].flatten.compact.uniq.map { |o| o.to_s }
- only &= @only if @only
- only
- end
- def _combine_excepts(except)
- return @except unless except
- except = [except].flatten.compact.uniq.map { |o| o.to_s }
- except |= @except if @except
- except
- end
- end
- context.new(env && env.to_s).instance_eval(File.read(@gemfile))
- end
-end
-
-<% if options[:rubygems] -%>
-module Gem
- def source_index.refresh!
- super
- Bundler.add_specs_to_index
- end
-end
-<% else -%>
-$" << "rubygems.rb"
-
-module Kernel
- def gem(*)
- # Silently ignore calls to gem, since, in theory, everything
- # is activated correctly already.
- end
-end
-
-# Define all the Gem errors for gems that reference them.
-module Gem
- def self.ruby ; <%= Gem.ruby.inspect %> ; end
- class LoadError < ::LoadError; end
- class Exception < RuntimeError; end
- class CommandLineError < Exception; end
- class DependencyError < Exception; end
- class DependencyRemovalException < Exception; end
- class GemNotInHomeException < Exception ; end
- class DocumentError < Exception; end
- class EndOfYAMLException < Exception; end
- class FilePermissionError < Exception; end
- class FormatException < Exception; end
- class GemNotFoundException < Exception; end
- class InstallError < Exception; end
- class InvalidSpecificationException < Exception; end
- class OperationNotSupportedError < Exception; end
- class RemoteError < Exception; end
- class RemoteInstallationCancelled < Exception; end
- class RemoteInstallationSkipped < Exception; end
- class RemoteSourceException < Exception; end
- class VerificationError < Exception; end
- class SystemExitException < SystemExit; end
-end
-<% end -%> \ No newline at end of file
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 2d6983076a..77ef82856a 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -17,7 +17,6 @@ require 'fileutils'
require 'active_support'
require 'active_support/core_ext/logger'
-require 'active_support/test_case'
require 'action_controller'
require 'rails/all'
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 0c858d6394..e1e51c318c 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -52,8 +52,8 @@ module ApplicationTests
config.generators.test_framework :rspec
RUBY
- require "#{app_path}/config/environment"
# Initialize the application
+ require "#{app_path}/config/environment"
require "rails/generators"
Rails::Generators.configure!
diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb
index 3fd0b0e5df..754c0f1839 100644
--- a/railties/test/application/initializer_test.rb
+++ b/railties/test/application/initializer_test.rb
@@ -83,6 +83,17 @@ module ApplicationTests
assert_equal "congratulations", $test_after_initialize_block2
end
+ test "after_initialize runs after frameworks have been initialized" do
+ $activerecord_configurations = nil
+ add_to_config <<-RUBY
+ config.after_initialize { $activerecord_configurations = ActiveRecord::Base.configurations }
+ RUBY
+
+ require "#{app_path}/config/environment"
+ assert $activerecord_configurations
+ assert $activerecord_configurations['development']
+ end
+
# i18n
test "setting another default locale" do
add_to_config <<-RUBY
diff --git a/railties/test/application/metal_test.rb b/railties/test/application/metal_test.rb
new file mode 100644
index 0000000000..225bede117
--- /dev/null
+++ b/railties/test/application/metal_test.rb
@@ -0,0 +1,86 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class MetalTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ end
+
+ def app
+ @app ||= begin
+ require "#{app_path}/config/environment"
+ Rails.application
+ end
+ end
+
+ test "single metal endpoint" do
+ app_file 'app/metal/foo_metal.rb', <<-RUBY
+ class FooMetal
+ def self.call(env)
+ [200, { "Content-Type" => "text/html"}, ["FooMetal"]]
+ end
+ end
+ RUBY
+
+ get "/"
+ assert_equal 200, last_response.status
+ assert_equal "FooMetal", last_response.body
+ end
+
+ test "multiple metal endpoints" do
+ app_file 'app/metal/metal_a.rb', <<-RUBY
+ class MetalA
+ def self.call(env)
+ [404, { "Content-Type" => "text/html", "X-Cascade" => "pass" }, ["Metal A"]]
+ end
+ end
+ RUBY
+
+ app_file 'app/metal/metal_b.rb', <<-RUBY
+ class MetalB
+ def self.call(env)
+ [200, { "Content-Type" => "text/html"}, ["Metal B"]]
+ end
+ end
+ RUBY
+
+ get "/"
+ assert_equal 200, last_response.status
+ assert_equal "Metal B", last_response.body
+ end
+
+ test "pass through to application" do
+ app_file 'app/metal/foo_metal.rb', <<-RUBY
+ class FooMetal
+ def self.call(env)
+ [404, { "Content-Type" => "text/html", "X-Cascade" => "pass" }, ["Not Found"]]
+ end
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ render :text => "foo"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match ':controller(/:action)'
+ end
+ RUBY
+
+ get "/foo"
+ assert_equal 200, last_response.status
+ assert_equal "foo", last_response.body
+ end
+ end
+end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
new file mode 100644
index 0000000000..7b3077bb6e
--- /dev/null
+++ b/railties/test/application/middleware_test.rb
@@ -0,0 +1,80 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class MiddlewareTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf "#{app_path}/config/environments"
+ end
+
+ test "default middleware stack" do
+ boot!
+
+ assert_equal [
+ "ActionDispatch::Static",
+ "Rack::Lock",
+ "Rack::Runtime",
+ "ActionDispatch::ShowExceptions",
+ "ActionDispatch::Callbacks",
+ "ActionDispatch::Session::CookieStore",
+ "ActionDispatch::Flash",
+ "ActionDispatch::Cascade",
+ "ActionDispatch::ParamsParser",
+ "Rack::MethodOverride",
+ "ActionDispatch::Head",
+ "ActiveRecord::ConnectionAdapters::ConnectionManagement",
+ "ActiveRecord::QueryCache"
+ ], middleware
+ end
+
+ test "removing activerecord omits its middleware" do
+ use_frameworks []
+ boot!
+ assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
+ assert !middleware.include?("ActiveRecord::QueryCache")
+ end
+
+ test "removes lock if allow concurrency is set" do
+ add_to_config "config.action_controller.allow_concurrency = true"
+ boot!
+ assert !middleware.include?("Rack::Lock")
+ end
+
+ test "removes static asset server if serve_static_assets is disabled" do
+ add_to_config "config.serve_static_assets = false"
+ boot!
+ assert !middleware.include?("ActionDispatch::Static")
+ end
+
+ test "use middleware" do
+ use_frameworks []
+ add_to_config "config.middleware.use Rack::Config"
+ boot!
+ assert_equal "Rack::Config", middleware.last
+ end
+
+ test "insert middleware after" do
+ add_to_config "config.middleware.insert_after ActionDispatch::Static, Rack::Config"
+ boot!
+ assert_equal "Rack::Config", middleware.second
+ end
+
+ test "insert middleware before" do
+ add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config"
+ boot!
+ assert_equal "Rack::Config", middleware.first
+ end
+
+ private
+ def boot!
+ require "#{app_path}/config/environment"
+ end
+
+ def middleware
+ AppTemplate::Application.instance.middleware.active.map(&:klass).map(&:name)
+ end
+ end
+end
diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb
index b57e829cca..1eb0777db8 100644
--- a/railties/test/application/notifications_test.rb
+++ b/railties/test/application/notifications_test.rb
@@ -12,28 +12,64 @@ module ApplicationTests
end
end
+ class MockLogger
+ def method_missing(*args)
+ @logged ||= []
+ @logged << args.last
+ end
+
+ def logged
+ @logged.compact.map { |l| l.to_s.strip }
+ end
+ end
+
class NotificationsTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
def setup
build_app
boot_rails
+ end
+
+ def instrument(*args, &block)
+ ActiveSupport::Notifications.instrument(*args, &block)
+ end
+
+ def wait
+ ActiveSupport::Notifications.notifier.wait
+ end
+
+ test "new queue is set" do
+ # We don't want to load all frameworks, so remove them and clean up environments.
+ use_frameworks []
FileUtils.rm_rf("#{app_path}/config/environments")
- require "active_support/notifications"
- @events = []
add_to_config <<-RUBY
config.notifications.notifier = ActiveSupport::Notifications::Notifier.new(ApplicationTests::MyQueue.new)
RUBY
- end
- test "new queue is set" do
- use_frameworks []
require "#{app_path}/config/environment"
assert_raise RuntimeError do
ActiveSupport::Notifications.publish('foo')
end
end
+
+ test "rails subscribers are added" do
+ add_to_config <<-RUBY
+ config.colorize_logging = false
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ ActiveRecord::Base.logger = logger = MockLogger.new
+
+ # Mimic an ActiveRecord notifications
+ instrument "active_record.sql", :name => "SQL", :sql => "SHOW tables"
+ wait
+
+ assert_equal 1, logger.logged.size
+ assert_match /SHOW tables/, logger.logged.last
+ end
end
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
new file mode 100644
index 0000000000..37175783d8
--- /dev/null
+++ b/railties/test/application/test_test.rb
@@ -0,0 +1,63 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class TestTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ test "truth" do
+ app_file 'test/unit/foo_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class FooTest < ActiveSupport::TestCase
+ def test_truth
+ assert true
+ end
+ end
+ RUBY
+
+ run_test 'unit/foo_test.rb'
+ end
+
+ test "integration test" do
+ controller 'posts', <<-RUBY
+ class PostsController < ActionController::Base
+ end
+ RUBY
+
+ app_file 'app/views/posts/index.html.erb', <<-HTML
+ Posts#index
+ HTML
+
+ app_file 'test/integration/posts_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostsTest < ActionController::IntegrationTest
+ def test_index
+ get '/posts'
+ assert_response :success
+ assert_template "index"
+ end
+ end
+ RUBY
+
+ run_test 'integration/posts_test.rb'
+ end
+
+ private
+ def run_test(name)
+ result = ruby '-Itest', "#{app_path}/test/#{name}"
+ assert_equal 0, $?.to_i, result
+ end
+
+ def ruby(*args)
+ Dir.chdir(app_path) do
+ `RUBYLIB='#{$:.join(':')}' #{Gem.ruby} #{args.join(' ')}`
+ end
+ end
+ end
+end
diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb
deleted file mode 100644
index 4ca4ddd447..0000000000
--- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class MetalA
- def self.call(env)
- [404, { "Content-Type" => "text/html"}, ["Metal A"]]
- end
-end
diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb
deleted file mode 100644
index 80e69fe0b0..0000000000
--- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class MetalB
- def self.call(env)
- [200, { "Content-Type" => "text/html"}, ["Metal B"]]
- end
-end
diff --git a/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb b/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb
deleted file mode 100644
index 0cd3737c32..0000000000
--- a/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class LegacyRoutes < Rails::Rack::Metal
- def self.call(env)
- [301, { "Location" => "http://example.com"}, []]
- end
-end
diff --git a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb b/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb
deleted file mode 100644
index 5f5b087592..0000000000
--- a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class FooMetal < Rails::Rack::Metal
- def self.call(env)
- [200, { "Content-Type" => "text/html"}, ["Hi"]]
- end
-end
diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb
deleted file mode 100644
index 25b3bb0abc..0000000000
--- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Folder
- class MetalA < Rails::Rack::Metal
- def self.call(env)
- [200, { "Content-Type" => "text/html"}, ["Hi"]]
- end
- end
-end
diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb
deleted file mode 100644
index 7583363f71..0000000000
--- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Folder
- class MetalB < Rails::Rack::Metal
- def self.call(env)
- [200, { "Content-Type" => "text/html"}, ["Hi"]]
- end
- end
-end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 7dd798db75..62ea07f14e 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -55,13 +55,19 @@ class AppGeneratorTest < GeneratorsTestCase
end
def test_invalid_application_name_raises_an_error
- content = capture(:stderr){ Rails::Generators::AppGenerator.start [File.join(destination_root, "43-things")] }
+ content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content
end
def test_invalid_application_name_is_fixed
- silence(:stdout){ Rails::Generators::AppGenerator.start [File.join(destination_root, "things-43")] }
- assert_file "things-43/config/environment.rb", /Things43::Application/
+ run_generator [File.join(destination_root, "things-43")]
+ assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/
+ assert_file "things-43/config/application.rb", /^module Things43$/
+ end
+
+ def test_application_names_are_not_singularized
+ run_generator [File.join(destination_root, "hats")]
+ assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/
end
def test_config_database_is_added_by_default
@@ -138,7 +144,7 @@ class AppGeneratorTest < GeneratorsTestCase
template = %{ say "It works!" }
template.instance_eval "def read; self; end" # Make the string respond to read
- generator([destination_root], :template => path, :database => "sqlite3").expects(:open).with(path).returns(template)
+ generator([destination_root], :template => path).expects(:open).with(path).returns(template)
assert_match /It works!/, silence(:stdout){ generator.invoke }
end
@@ -162,14 +168,16 @@ class AppGeneratorTest < GeneratorsTestCase
end
def test_dev_option
- run_generator [destination_root, "--dev"]
+ generator([destination_root], :dev => true).expects(:run).with("gem bundle")
+ silence(:stdout){ generator.invoke }
rails_path = File.expand_path('../../..', Rails.root)
- dev_gem = %(gem "rails", :path => #{rails_path.inspect})
+ dev_gem = %(directory #{rails_path.inspect}, :glob => "{*/,}*.gemspec")
assert_file 'Gemfile', /^#{Regexp.escape(dev_gem)}$/
end
def test_edge_option
- run_generator [destination_root, "--edge"]
+ generator([destination_root], :edge => true).expects(:run).with("gem bundle")
+ silence(:stdout){ generator.invoke }
edge_gem = %(gem "rails", :git => "git://github.com/rails/rails.git")
assert_file 'Gemfile', /^#{Regexp.escape(edge_gem)}$/
end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index fcd0989fd7..54953b76c8 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,5 +1,3 @@
-# TODO: Fix this RAILS_ENV stuff
-RAILS_ENV = 'test' unless defined?(RAILS_ENV)
require 'abstract_unit'
module Rails
@@ -25,4 +23,8 @@ class GeneratorsTestCase < Rails::Generators::TestCase
rescue
# Do nothing.
end
+
+ def test_truth
+ # Don't cry test/unit
+ end
end \ No newline at end of file
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index bfb1887d11..328dda6d2c 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -63,7 +63,7 @@ module InitializerTests
end
test "environments has a glob equal to the current environment" do
- assert_equal "#{RAILS_ENV}.rb", @paths.config.environments.glob
+ assert_equal "#{Rails.env}.rb", @paths.config.environments.glob
end
test "load path includes each of the paths in config.paths as long as the directories exist" do
@@ -85,17 +85,17 @@ module InitializerTests
end
test "controller paths include builtin in development mode" do
- RAILS_ENV.replace "development"
+ Rails.env.replace "development"
assert Rails::Configuration.new.paths.app.controllers.paths.any? { |p| p =~ /builtin/ }
end
test "controller paths does not have builtin_directories in test mode" do
- RAILS_ENV.replace "test"
+ Rails.env.replace "test"
assert !Rails::Configuration.new.paths.app.controllers.paths.any? { |p| p =~ /builtin/ }
end
test "controller paths does not have builtin_directories in production mode" do
- RAILS_ENV.replace "production"
+ Rails.env.replace "production"
assert !Rails::Configuration.new.paths.app.controllers.paths.any? { |p| p =~ /builtin/ }
end
diff --git a/railties/test/metal_test.rb b/railties/test/metal_test.rb
deleted file mode 100644
index 91f55c2b5e..0000000000
--- a/railties/test/metal_test.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'abstract_unit'
-
-class MetalTest < Test::Unit::TestCase
- def test_metals_should_return_list_of_found_metal_apps
- use_appdir("singlemetal") do
- assert_equal(["FooMetal"], found_metals_as_string_array)
- end
- end
-
- def test_metals_should_respect_class_name_conventions
- use_appdir("pluralmetal") do
- assert_equal(["LegacyRoutes"], found_metals_as_string_array)
- end
- end
-
- def test_metals_should_return_alphabetical_list_of_found_metal_apps
- use_appdir("multiplemetals") do
- assert_equal(["MetalA", "MetalB"], found_metals_as_string_array)
- end
- end
-
- def test_metals_load_order_should_be_overriden_by_requested_metals
- use_appdir("multiplemetals") do
- Rails::Rack::Metal.requested_metals = ["MetalB", "MetalA"]
- assert_equal(["MetalB", "MetalA"], found_metals_as_string_array)
- end
- end
-
- def test_metals_not_listed_should_not_load
- use_appdir("multiplemetals") do
- Rails::Rack::Metal.requested_metals = ["MetalB"]
- assert_equal(["MetalB"], found_metals_as_string_array)
- end
- end
-
- def test_metal_finding_should_work_with_subfolders
- use_appdir("subfolders") do
- assert_equal(["Folder::MetalA", "Folder::MetalB"], found_metals_as_string_array)
- end
- end
-
- def test_metal_finding_with_requested_metals_should_work_with_subfolders
- use_appdir("subfolders") do
- Rails::Rack::Metal.requested_metals = ["Folder::MetalB"]
- assert_equal(["Folder::MetalB"], found_metals_as_string_array)
- end
- end
-
- def test_metal_finding_should_work_with_multiple_metal_paths_in_185_and_below
- use_appdir("singlemetal") do
- engine_metal_path = "#{File.dirname(__FILE__)}/fixtures/plugins/engines/engine/app/metal"
- Rails::Rack::Metal.metal_paths << engine_metal_path
- $LOAD_PATH << engine_metal_path
- assert_equal(["FooMetal", "EngineMetal"], found_metals_as_string_array)
- end
- end
-
- def test_metal_default_pass_through_on_404
- use_appdir("multiplemetals") do
- result = Rails::Rack::Metal.new(app).call({})
- assert_equal 200, result.first
- assert_equal ["Metal B"], result.last
- end
- end
-
- def test_metal_pass_through_on_417
- use_appdir("multiplemetals") do
- Rails::Rack::Metal.pass_through_on = 417
- result = Rails::Rack::Metal.new(app).call({})
- assert_equal 404, result.first
- assert_equal ["Metal A"], result.last
- end
- end
-
- def test_metal_pass_through_on_404_and_200
- use_appdir("multiplemetals") do
- Rails::Rack::Metal.pass_through_on = [404, 200]
- result = Rails::Rack::Metal.new(app).call({})
- assert_equal 402, result.first
- assert_equal ["End of the Line"], result.last
- end
- end
-
- private
-
- def app
- lambda{|env|[402,{},["End of the Line"]]}
- end
-
- def use_appdir(root)
- dir = "#{File.dirname(__FILE__)}/fixtures/metal/#{root}"
- Rails::Rack::Metal.metal_paths = ["#{dir}/app/metal"]
- Rails::Rack::Metal.requested_metals = nil
- $LOAD_PATH << "#{dir}/app/metal"
- yield
- end
-
- def found_metals_as_string_array
- Rails::Rack::Metal.metals.map { |m| m.to_s }
- end
-end
diff --git a/railties/test/plugins/configuration_test.rb b/railties/test/plugins/configuration_test.rb
index 25bf24eb3b..09f8943af9 100644
--- a/railties/test/plugins/configuration_test.rb
+++ b/railties/test/plugins/configuration_test.rb
@@ -32,5 +32,14 @@ module PluginsTest
assert_equal "hello", MyApp.config.foo.greetings
assert_equal "bar", MyApp.config.foo.bar
end
+
+ test "plugin can add subscribers" do
+ begin
+ class Foo < Rails::Railtie; subscriber(Rails::Subscriber.new); end
+ assert_kind_of Rails::Subscriber, Rails::Subscriber.subscribers[:foo]
+ ensure
+ Rails::Subscriber.subscribers.clear
+ end
+ end
end
end
diff --git a/railties/test/plugins/vendored_test.rb b/railties/test/plugins/vendored_test.rb
index 9a2d40cad8..b3b85891b2 100644
--- a/railties/test/plugins/vendored_test.rb
+++ b/railties/test/plugins/vendored_test.rb
@@ -191,5 +191,11 @@ module PluginsTest
boot_rails
assert_equal [:a, :c, :b], $arr
end
+
+ test "plugin order array is strings" do
+ add_to_config "config.plugins = %w( c_plugin all )"
+ boot_rails
+ assert_equal [:c, :a, :b], $arr
+ end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 435bd34925..edab27465e 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
require 'action_controller'
-require 'action_controller/test_case'
require 'rails/info'
require 'rails/info_controller'
diff --git a/railties/test/subscriber_test.rb b/railties/test/subscriber_test.rb
new file mode 100644
index 0000000000..ac34939510
--- /dev/null
+++ b/railties/test/subscriber_test.rb
@@ -0,0 +1,130 @@
+require 'abstract_unit'
+require 'rails/subscriber/test_helper'
+
+class MySubscriber < Rails::Subscriber
+ attr_reader :event
+
+ def some_event(event)
+ @event = event
+ info event.name
+ end
+
+ def foo(event)
+ debug "debug"
+ info "info"
+ warn "warn"
+ end
+
+ def bar(event)
+ info "#{color("cool", :red)}, #{color("isn't it?", :blue, true)}"
+ end
+end
+
+module SubscriberTest
+ def setup
+ super
+ @subscriber = MySubscriber.new
+ Rails::Subscriber.instance_variable_set(:@log_tailer, nil)
+ end
+
+ def teardown
+ super
+ Rails::Subscriber.subscribers.clear
+ Rails::Subscriber.instance_variable_set(:@log_tailer, nil)
+ end
+
+ def instrument(*args, &block)
+ ActiveSupport::Notifications.instrument(*args, &block)
+ end
+
+ def test_proxies_method_to_rails_logger
+ @subscriber.foo(nil)
+ assert_equal %w(debug), @logger.logged(:debug)
+ assert_equal %w(info), @logger.logged(:info)
+ assert_equal %w(warn), @logger.logged(:warn)
+ end
+
+ def test_set_color_for_messages
+ Rails::Subscriber.colorize_logging = true
+ @subscriber.bar(nil)
+ assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last
+ end
+
+ def test_does_not_set_color_if_colorize_logging_is_set_to_false
+ @subscriber.bar(nil)
+ assert_equal "cool, isn't it?", @logger.logged(:info).last
+ end
+
+ def test_event_is_sent_to_the_registered_class
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "my_subscriber.some_event"
+ wait
+ assert_equal %w(my_subscriber.some_event), @logger.logged(:info)
+ end
+
+ def test_event_is_an_active_support_notifications_event
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "my_subscriber.some_event"
+ wait
+ assert_kind_of ActiveSupport::Notifications::Event, @subscriber.event
+ end
+
+ def test_does_not_send_the_event_if_it_doesnt_match_the_class
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "my_subscriber.unknown_event"
+ wait
+ # If we get here, it means that NoMethodError was raised.
+ end
+
+ def test_does_not_send_the_event_if_logger_is_nil
+ Rails.logger = nil
+ @subscriber.expects(:some_event).never
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "my_subscriber.some_event"
+ wait
+ end
+
+ def test_flushes_loggers
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ Rails::Subscriber.flush_all!
+ assert_equal 1, @logger.flush_count
+ end
+
+ def test_flushes_loggers_when_action_dispatch_callback_is_received
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "action_dispatch.callback"
+ wait
+ assert_equal 1, @logger.flush_count
+ end
+
+ def test_flushes_the_same_logger_just_once
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ Rails::Subscriber.add :another, @subscriber
+ instrument "action_dispatch.callback"
+ wait
+ assert_equal 1, @logger.flush_count
+ end
+
+ def test_tails_logs_when_action_dispatch_callback_is_received
+ log_tailer = mock()
+ log_tailer.expects(:tail!)
+ Rails::Subscriber.log_tailer = log_tailer
+
+ Rails::Subscriber.add :my_subscriber, @subscriber
+ instrument "action_dispatch.callback"
+ wait
+ ensure
+ Rails::Subscriber.log_tailer = nil
+ end
+
+ class SyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::SyncTestHelper
+ include SubscriberTest
+ end
+
+ class AsyncSubscriberTest < ActiveSupport::TestCase
+ include Rails::Subscriber::AsyncTestHelper
+ include SubscriberTest
+ end
+
+end \ No newline at end of file