aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2011-04-17 17:08:49 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2011-04-17 17:08:49 +0100
commit1c2b2233c3a7ec76c0a0eddf5b8be45c489be133 (patch)
tree56f2b767c3a4f1f14c51606bf2cbb714a98c5f89
parent8d558cb1b069410c8f693295c9c4e2ffc9661e06 (diff)
parentb6843f22ac42b503f6b8ac00105ca0679049be7d (diff)
downloadrails-1c2b2233c3a7ec76c0a0eddf5b8be45c489be133.tar.gz
rails-1c2b2233c3a7ec76c0a0eddf5b8be45c489be133.tar.bz2
rails-1c2b2233c3a7ec76c0a0eddf5b8be45c489be133.zip
Merge branch 'master' of https://github.com/rails/rails into performance_test
-rw-r--r--Gemfile5
-rw-r--r--README.rdoc2
-rw-r--r--actionmailer/README.rdoc15
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb31
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb8
-rw-r--r--actionmailer/test/base_test.rb40
-rw-r--r--actionpack/CHANGELOG4
-rw-r--r--actionpack/README.rdoc7
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb3
-rw-r--r--actionpack/lib/action_controller/base.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb20
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb9
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb68
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb204
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb28
-rw-r--r--actionpack/lib/action_view.rb11
-rw-r--r--actionpack/lib/action_view/base.rb47
-rw-r--r--actionpack/lib/action_view/buffers.rb43
-rw-r--r--actionpack/lib/action_view/flows.rb79
-rw-r--r--actionpack/lib/action_view/helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb3
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb142
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb14
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb23
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb119
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb852
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb263
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb85
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb7
-rw-r--r--actionpack/lib/action_view/path_set.rb1
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb129
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb30
-rw-r--r--actionpack/lib/action_view/rendering.rb38
-rw-r--r--actionpack/lib/action_view/template.rb37
-rw-r--r--actionpack/lib/action_view/template/handlers.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb22
-rw-r--r--actionpack/lib/action_view/template/handlers/rjs.rb13
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb2
-rw-r--r--actionpack/lib/sprockets/railtie.rb56
-rw-r--r--actionpack/test/controller/assert_select_test.rb414
-rw-r--r--actionpack/test/controller/caching_test.rb14
-rw-r--r--actionpack/test/controller/content_type_test.rb9
-rw-r--r--actionpack/test/controller/flash_hash_test.rb90
-rw-r--r--actionpack/test/controller/flash_test.rb58
-rw-r--r--actionpack/test/controller/integration_test.rb8
-rw-r--r--actionpack/test/controller/mime_responds_test.rb32
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb9
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb13
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb16
-rw-r--r--actionpack/test/controller/new_base/render_once_test.rb86
-rw-r--r--actionpack/test/controller/new_base/render_rjs_test.rb71
-rw-r--r--actionpack/test/controller/new_base/render_test.rb3
-rw-r--r--actionpack/test/controller/render_js_test.rb9
-rw-r--r--actionpack/test/controller/render_other_test.rb237
-rw-r--r--actionpack/test/controller/render_test.rb9
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb8
-rw-r--r--actionpack/test/controller/resources_test.rb7
-rw-r--r--actionpack/test/controller/view_paths_test.rb29
-rw-r--r--actionpack/test/dispatch/cookies_test.rb96
-rw-r--r--actionpack/test/dispatch/routing_test.rb3
-rw-r--r--actionpack/test/dispatch/static_test.rb33
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs6
-rw-r--r--actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs1
-rw-r--r--actionpack/test/fixtures/layouts/streaming.erb4
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.rjs1
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/application.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/application.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/style.css0
-rw-r--r--actionpack/test/fixtures/test/delete_with_js.rjs2
-rw-r--r--actionpack/test/fixtures/test/enum_rjs_test.rjs6
-rw-r--r--actionpack/test/fixtures/test/greeting.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/nested_streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/render_explicit_html_template.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/streaming_buster.erb3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb23
-rw-r--r--actionpack/test/template/capture_helper_test.rb16
-rw-r--r--actionpack/test/template/date_helper_test.rb6
-rw-r--r--actionpack/test/template/erb_util_test.rb3
-rw-r--r--actionpack/test/template/form_helper_test.rb3
-rw-r--r--actionpack/test/template/form_options_helper_test.rb3
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb3
-rw-r--r--actionpack/test/template/javascript_helper_test.rb39
-rw-r--r--actionpack/test/template/lookup_context_test.rb25
-rw-r--r--actionpack/test/template/prototype_helper_test.rb476
-rw-r--r--actionpack/test/template/render_test.rb13
-rw-r--r--actionpack/test/template/scriptaculous_helper_test.rb86
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb96
-rw-r--r--actionpack/test/template/streaming_render_test.rb105
-rw-r--r--actionpack/test/template/template_test.rb38
-rw-r--r--actionpack/test/template/text_helper_test.rb16
-rw-r--r--activemodel/CHANGELOG9
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb21
-rw-r--r--activemodel/lib/active_model/secure_password.rb7
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb27
-rw-r--r--activemodel/lib/active_model/validations/format.rb46
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb26
-rw-r--r--activemodel/lib/active_model/validator.rb3
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb3
-rw-r--r--activemodel/test/cases/secure_password_test.rb12
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb (renamed from activemodel/test/cases/serializeration/json_serialization_test.rb)0
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb (renamed from activemodel/test/cases/serializeration/xml_serialization_test.rb)0
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb24
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb12
-rw-r--r--activerecord/CHANGELOG52
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb22
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb35
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb3
-rw-r--r--activerecord/lib/active_record/base.rb141
-rw-r--r--activerecord/lib/active_record/callbacks.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb85
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb231
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb13
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/identity_map.rb9
-rw-r--r--activerecord/lib/active_record/named_scope.rb113
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb7
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/reflection.rb3
-rw-r--r--activerecord/lib/active_record/relation.rb48
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb14
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb4
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb38
-rw-r--r--activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb69
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb26
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb69
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb25
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb229
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb93
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb231
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb13
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb5
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb15
-rw-r--r--activerecord/test/cases/base_test.rb22
-rw-r--r--activerecord/test/cases/binary_test.rb19
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb3
-rw-r--r--activerecord/test/cases/finder_test.rb3
-rw-r--r--activerecord/test/cases/identity_map_test.rb22
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb3
-rw-r--r--activerecord/test/cases/method_scoping_test.rb12
-rw-r--r--activerecord/test/cases/named_scope_test.rb42
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb8
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/quoting_test.rb13
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb240
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/models/bulb.rb13
-rw-r--r--activerecord/test/models/car.rb9
-rw-r--r--activerecord/test/models/categorization.rb4
-rw-r--r--activerecord/test/models/comment.rb5
-rw-r--r--activerecord/test/models/developer.rb71
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/models/post.rb36
-rw-r--r--activerecord/test/models/reference.rb7
-rw-r--r--activerecord/test/models/topic.rb26
-rw-r--r--activerecord/test/models/without_table.rb6
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activeresource/lib/active_resource/connection.rb3
-rw-r--r--activeresource/lib/active_resource/http_mock.rb3
-rw-r--r--activeresource/test/cases/http_mock_test.rb3
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb5
-rw-r--r--activesupport/lib/active_support/callbacks.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb41
-rw-r--r--activesupport/lib/active_support/railtie.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb5
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb9
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb50
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb47
-rw-r--r--activesupport/test/ordered_options_test.rb26
-rw-r--r--activesupport/test/transliterate_test.rb3
-rwxr-xr-xbin/rails6
-rw-r--r--load_paths.rb39
-rw-r--r--railties/CHANGELOG20
-rw-r--r--railties/guides/rails_guides/textile_extensions.rb6
-rw-r--r--railties/guides/source/2_3_release_notes.textile2
-rw-r--r--railties/guides/source/action_controller_overview.textile17
-rw-r--r--railties/guides/source/action_view_overview.textile2
-rw-r--r--railties/guides/source/active_record_querying.textile2
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile2
-rw-r--r--railties/guides/source/active_support_core_extensions.textile22
-rw-r--r--railties/guides/source/ajax_on_rails.textile143
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile2
-rw-r--r--railties/guides/source/command_line.textile6
-rw-r--r--railties/guides/source/configuring.textile4
-rw-r--r--railties/guides/source/debugging_rails_applications.textile20
-rw-r--r--railties/guides/source/generators.textile2
-rw-r--r--railties/guides/source/getting_started.textile66
-rw-r--r--railties/guides/source/initialization.textile5
-rw-r--r--railties/guides/source/layout.html.erb2
-rw-r--r--railties/guides/source/layouts_and_rendering.textile30
-rw-r--r--railties/guides/source/ruby_on_rails_guides_guidelines.textile19
-rw-r--r--railties/guides/source/security.textile6
-rw-r--r--railties/guides/source/testing.textile10
-rw-r--r--railties/lib/rails/application.rb13
-rw-r--r--railties/lib/rails/application/configuration.rb21
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/commands.rb4
-rw-r--r--railties/lib/rails/commands/application.rb3
-rw-r--r--railties/lib/rails/commands/benchmarker.rb4
-rw-r--r--railties/lib/rails/commands/destroy.rb4
-rw-r--r--railties/lib/rails/commands/generate.rb4
-rw-r--r--railties/lib/rails/commands/profiler.rb4
-rw-r--r--railties/lib/rails/engine.rb62
-rw-r--r--railties/lib/rails/engine/configuration.rb11
-rw-r--r--railties/lib/rails/generators.rb9
-rw-r--r--railties/lib/rails/generators/app_base.rb68
-rw-r--r--railties/lib/rails/generators/base.rb3
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb3
-rw-r--r--railties/lib/rails/generators/named_base.rb13
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb86
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/db/seeds.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb (renamed from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js (renamed from railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js)0
-rw-r--r--railties/lib/rails/generators/rails/assets/USAGE20
-rw-r--r--railties/lib/rails/generators/rails/assets/assets_generator.rb39
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/javascript.js2
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee3
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css4
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss5
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb64
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb21
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css (renamed from railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css)0
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss58
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb38
-rw-r--r--railties/lib/rails/generators/rails/stylesheets/USAGE5
-rw-r--r--railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/railtie/configuration.rb7
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb10
-rw-r--r--railties/lib/rails/tasks.rb2
-rw-r--r--railties/lib/rails/tasks/assets.rake6
-rw-r--r--railties/lib/rails/tasks/railties.rake29
-rw-r--r--railties/lib/rails/test_help.rb10
-rw-r--r--railties/railties.gemspec1
-rw-r--r--railties/test/application/configuration_test.rb87
-rw-r--r--railties/test/generators/app_generator_test.rb72
-rw-r--r--railties/test/generators/assets_generator_test.rb26
-rw-r--r--railties/test/generators/controller_generator_test.rb6
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb49
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb18
-rw-r--r--railties/test/generators/scaffold_generator_test.rb52
-rw-r--r--railties/test/generators/stylesheets_generator_test.rb17
-rw-r--r--railties/test/isolation/abstract_unit.rb3
-rw-r--r--railties/test/railties/engine_test.rb258
-rw-r--r--railties/test/railties/shared_tests.rb42
323 files changed, 4286 insertions, 5204 deletions
diff --git a/Gemfile b/Gemfile
index 879598c6db..a94f3689bc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +11,10 @@ end
gem "rack", :git => "git://github.com/rack/rack.git"
gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
+gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git"
+gem "coffee-script"
+gem "sass", ">= 3.0"
+
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
@@ -28,6 +32,7 @@ platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
gem 'ruby-prof'
+ gem "json"
end
platforms :mri_19 do
diff --git a/README.rdoc b/README.rdoc
index 0b209cf56f..216a122c66 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -48,7 +48,7 @@ more separate. Each of these packages can be used independently outside of
"Welcome aboard: You're riding Ruby on Rails!"
-5. Follow the guidelines to start developing your application. You can find the following resources handy:
+5. Follow the guidelines to start developing your application. You may find the following resources handy:
* The README file created within your application.
* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 0fa18d751b..9b206fbcc7 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -32,7 +32,7 @@ This can be as simple as:
end
The body of the email is created by using an Action View template (regular
-ERb) that has the instance variables that are declared in the mailer action.
+ERB) that has the instance variables that are declared in the mailer action.
So the corresponding body template for the method above could look like this:
@@ -72,6 +72,19 @@ Or you can just chain the methods together like:
Notifier.welcome.deliver # Creates the email and sends it immediately
+== Setting defaults
+
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you wont need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
+
+Note that every value you set with this method will get over written if you use the same key in your mailer method.
+
+Example:
+
+ class Authenticationmailer < ActionMailer::Base
+ default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
+ .....
+ end
+
== Receiving emails
To receive emails, you need to implement a public instance method called <tt>receive</tt> that takes an
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index ee02cf6945..a59069cc37 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -18,5 +18,5 @@ Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.2.15')
+ s.add_dependency('mail', '~> 2.2.16')
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 16fcf112b7..1bde73563e 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -4,6 +4,8 @@ require 'action_mailer/collector'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/proc'
+require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/hash/except'
require 'action_mailer/log_subscriber'
module ActionMailer #:nodoc:
@@ -349,9 +351,6 @@ module ActionMailer #:nodoc:
helper ActionMailer::MailHelper
include ActionMailer::OldApi
- delegate :register_observer, :to => Mail
- delegate :register_interceptor, :to => Mail
-
private_class_method :new #:nodoc:
class_attribute :default_params
@@ -363,6 +362,32 @@ module ActionMailer #:nodoc:
}.freeze
class << self
+ # Register one or more Observers which will be notified when mail is delivered.
+ def register_observers(*observers)
+ observers.flatten.compact.each { |observer| register_observer(observer) }
+ end
+
+ # Register one or more Interceptors which will be called before mail is sent.
+ def register_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
+ end
+
+ # Register an Observer which will be notified when mail is delivered.
+ # Either a class or a string can be passed in as the Observer. If a string is passed in
+ # it will be <tt>constantize</tt>d.
+ def register_observer(observer)
+ delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
+ Mail.register_observer(delivery_observer)
+ end
+
+ # Register an Inteceptor which will be called before mail is sent.
+ # Either a class or a string can be passed in as the Observer. If a string is passed in
+ # it will be <tt>constantize</tt>d.
+ def register_interceptor(interceptor)
+ delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
+ Mail.register_interceptor(delivery_interceptor)
+ end
+
def mailer_name
@mailer_name ||= name.underscore
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 4ec478067f..444754d0e9 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,13 +19,17 @@ module ActionMailer
options.stylesheets_dir ||= paths["public/stylesheets"].first
# make sure readers methods get compiled
- options.asset_path ||= app.config.asset_path
- options.asset_host ||= app.config.asset_host
+ options.asset_path ||= app.config.asset_path
+ options.asset_host ||= app.config.asset_host
ActiveSupport.on_load(:action_mailer) do
include AbstractController::UrlFor
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
include app.routes.mounted_helpers
+
+ register_interceptors(options.delete(:interceptors))
+ register_observers(options.delete(:observers))
+
options.each { |k,v| send("#{k}=", v) }
end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 6a7931da8c..9fdd0e1ced 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -478,6 +478,11 @@ class BaseTest < ActiveSupport::TestCase
end
end
+ class MySecondObserver
+ def self.delivered_email(mail)
+ end
+ end
+
test "you can register an observer to the mail object that gets informed on email delivery" do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
@@ -485,11 +490,31 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
+ ActionMailer::Base.register_observer("BaseTest::MyObserver")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
+ test "you can register multiple observers to the mail object that both get informed on email delivery" do
+ ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ MySecondObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
class MyInterceptor
def self.delivering_email(mail)
end
end
+ class MySecondInterceptor
+ def self.delivering_email(mail)
+ end
+ end
+
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
@@ -497,6 +522,21 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
+ test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ MySecondInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome
yesterday = 1.day.ago
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 3eba2281c4..76dbfe7895 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.1.0 (unreleased)*
+* RJS has been extracted out to a gem. [fxn]
+
+* Implicit actions named not_implemented can be rendered [Santiago Pastorino]
+
* Wildcard route will always matching the optional format segment by default. For example if you have this route:
map '*pages' => 'pages#show'
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 3661d27d51..1a56c44db5 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -19,9 +19,8 @@ It consists of several modules:
* Action View, which handles view template lookup and rendering, and provides
view helpers that assist when building HTML forms, Atom feeds and more.
- Template formats that Action View handles are ERb (embedded Ruby, typically
- used to inline short Ruby snippets inside HTML), XML Builder and RJS
- (dynamically generated JavaScript from Ruby code).
+ Template formats that Action View handles are ERB (embedded Ruby, typically
+ used to inline short Ruby snippets inside HTML), and XML Builder.
With the Ruby on Rails framework, users only directly interface with the
Action Controller module. Necessary Action Dispatch functionality is activated
@@ -57,7 +56,7 @@ A short rundown of some of the major features:
{Learn more}[link:classes/ActionController/Base.html]
-* ERb templates (static content mixed with dynamic output from ruby)
+* ERB templates (static content mixed with dynamic output from ruby)
<% for post in @posts %>
Title: <%= post.title %>
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 6c55842eea..d3c66800d9 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency('rack-test', '~> 0.5.7')
s.add_dependency('rack-mount', '~> 0.7.1')
s.add_dependency('tzinfo', '~> 0.3.23')
- s.add_dependency('erubis', '~> 2.6.6')
+ s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 9ca2fb742f..ad14cd6d87 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir, :use_sprockets
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 07ff5ad9f3..0951267fea 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,3 +1,4 @@
+require 'erubis'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
@@ -18,6 +19,7 @@ module AbstractController
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
+ undef_method :not_implemented
class << self
attr_reader :abstract
alias_method :abstract?, :abstract
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 95992c2698..f7b2b7ff53 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -29,7 +29,7 @@ module AbstractController
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except<tt> - The callback should be run for all actions except this action
+ # * <tt>except</tt> - The callback should be run for all actions except this action
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 4ee54474cc..d1b87b67ee 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -334,7 +334,7 @@ module AbstractController
# ==== Parameters
# * <tt>details</tt> - A list of details to restrict the search by. This
# might include details like the format or locale of the template.
- # * <tt>require_logout</tt> - If this is true, raise an ArgumentError
+ # * <tt>require_layout</tt> - If this is true, raise an ArgumentError
# with details about the fact that the exception could not be
# found (defaults to false)
#
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 691310d5d2..66f6d0eebb 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require "abstract_controller/base"
require "action_view"
+require "active_support/core_ext/object/instance_variables"
module AbstractController
class DoubleRenderError < Error
@@ -173,7 +174,7 @@ module AbstractController
options[:partial] = action_name
end
- if (options.keys & [:partial, :file, :template, :once]).empty?
+ if (options.keys & [:partial, :file, :template]).empty?
options[:prefixes] ||= _prefixes
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index e6523e56d2..5f9e082cd3 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -105,7 +105,7 @@ module ActionController
# == Renders
#
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
- # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
+ # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
# The controller passes objects to the view by assigning instance variables:
#
# def show
@@ -128,7 +128,7 @@ module ActionController
# end
# end
#
- # Read more about writing ERb and Builder templates in ActionView::Base.
+ # Read more about writing ERB and Builder templates in ActionView::Base.
#
# == Redirects
#
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index e5db31061b..585bd5e5ab 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -201,19 +201,23 @@ module ActionController
class_attribute :middleware_stack
self.middleware_stack = ActionController::MiddlewareStack.new
- def self.inherited(base)
+ def self.inherited(base) #nodoc:
base.middleware_stack = self.middleware_stack.dup
super
end
+ # Adds given middleware class and its args to bottom of middleware_stack
def self.use(*args, &block)
middleware_stack.use(*args, &block)
end
+ # Alias for middleware_stack
def self.middleware
middleware_stack
end
+ # Makes the controller a rack endpoint that points to the action in
+ # the given env's action_dispatch.request.path_parameters key.
def self.call(env)
action(env['action_dispatch.request.path_parameters'][:action]).call(env)
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index b98429792d..1d6df89007 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -67,7 +67,7 @@ module ActionController
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
#
# before_filter :authenticate, :except => [:index]
#
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 7a8fa7bd86..16d48e4677 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,8 +1,9 @@
require 'abstract_controller/collector'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/object/inclusion'
module ActionController #:nodoc:
- module MimeResponds #:nodoc:
+ module MimeResponds
extend ActiveSupport::Concern
included do
@@ -32,10 +33,10 @@ module ActionController #:nodoc:
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
# <tt>:json</tt>.
#
- # respond_to :rjs, :only => :create
+ # respond_to :json, :only => :create
#
# This specifies that the <tt>:create</tt> action and no other responds
- # to <tt>:rjs</tt>.
+ # to <tt>:json</tt>.
def respond_to(*mimes)
options = mimes.extract_options!
@@ -105,8 +106,8 @@ module ActionController #:nodoc:
# end
# end
#
- # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
- # (format.js), then it is an RJS request and we render the RJS template associated with this action.
+ # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
+ # then it is an Ajax request and we render the JavaScript template associated with this action.
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
# include the person's company in the rendered XML, so you get something like this:
#
@@ -222,6 +223,9 @@ module ActionController #:nodoc:
# is quite simple (it just needs to respond to call), you can even give
# a proc to it.
#
+ # In order to use respond_with, first you need to declare the formats your
+ # controller responds to in the class level with a call to <tt>respond_to</tt>.
+ #
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
@@ -245,9 +249,9 @@ module ActionController #:nodoc:
config = self.class.mimes_for_respond_to[mime]
if config[:except]
- !config[:except].include?(action)
+ !action.in?(config[:except])
elsif config[:only]
- config[:only].include?(action)
+ action.in?(config[:only])
else
true
end
@@ -257,7 +261,7 @@ 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=nil, &block)
+ def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes) { |options| default_render(options || {}) }
block.call(collector) if block_given?
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 38711c8462..dfda6618e7 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -41,7 +41,7 @@ module ActionController
end
# Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are :json, :js, :xml and :update.
+ # Default keys are :json, :js, :xml.
RENDERERS = {}
# Adds a new renderer to call within controller actions.
@@ -107,12 +107,5 @@ module ActionController
self.content_type ||= Mime::XML
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
-
- add :update do |proc, options|
- view_context = self.view_context
- generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
- self.content_type = Mime::JS
- self.response_body = generator.to_s
- end
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..d2ba052c8d 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,6 +4,7 @@ require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/paths"
+require "sprockets/railtie"
module ActionController
class Railtie < Rails::Railtie
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index dce3c2fe88..699c44c62c 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -16,14 +16,6 @@ module ActionController
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
-
- if app.config.serve_static_assets && namespace
- paths = namespace._railtie.config.paths
-
- klass.config.assets_dir = paths["public"].first
- klass.config.javascripts_dir = paths["public/javascripts"].first
- klass.config.stylesheets_dir = paths["public/stylesheets"].first
- end
end
end
end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 3de40b0de3..2def78b51a 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -18,18 +18,12 @@ module ActionController
# post = Post.find(params[:id])
# post.destroy
#
- # respond_to do |format|
- # format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
- # format.js do
- # # Calls: new Effect.fade('post_45');
- # render(:update) { |page| page[post].visual_effect(:fade) }
- # end
- # end
+ # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
- # convention and allows you to write less code if you follow it.
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
+ # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
+ # same naming convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 49971fc9f8..7f972fc281 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -60,6 +60,7 @@ module ActionDispatch
autoload :Static
end
+ autoload :ClosedError, 'action_dispatch/middleware/closed_error'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
new file mode 100644
index 0000000000..0a4db47f4b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/closed_error.rb
@@ -0,0 +1,7 @@
+module ActionDispatch
+ class ClosedError < StandardError #:nodoc:
+ def initialize(kind)
+ super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers."
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 7ac608f0a8..24ebb8fed7 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -83,7 +83,7 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
- class CookieJar < Hash #:nodoc:
+ class CookieJar #:nodoc:
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,13 +115,22 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
-
- super()
+ @closed = false
+ @cookies = {}
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
- super(name.to_s)
+ @cookies[name.to_s]
+ end
+
+ def update(other_hash)
+ @cookies.update other_hash
+ self
end
def handle_options(options) #:nodoc:
@@ -145,6 +154,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -153,7 +163,7 @@ module ActionDispatch
options = { :value => value }
end
- value = super(key.to_s, value)
+ value = @cookies[key.to_s] = value
handle_options(options)
@@ -170,7 +180,7 @@ module ActionDispatch
handle_options(options)
- value = super(key.to_s)
+ value = @cookies.delete(key.to_s)
@delete_cookies[key] = options
value
end
@@ -225,6 +235,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -263,6 +274,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -305,6 +317,7 @@ module ActionDispatch
end
def call(env)
+ cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
@@ -315,6 +328,9 @@ module ActionDispatch
end
[status, headers, body]
+ ensure
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar
+ cookie_jar.close!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 21aeeb217a..027ff7f8ac 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -43,9 +43,15 @@ module ActionDispatch
class FlashNow #:nodoc:
def initialize(flash)
@flash = flash
+ @closed = false
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
def []=(k, v)
+ raise ClosedError, :flash if closed?
@flash[k] = v
@flash.discard(k)
v
@@ -66,27 +72,70 @@ module ActionDispatch
end
end
- class FlashHash < Hash
+ class FlashHash
+ include Enumerable
+
def initialize #:nodoc:
- super
- @used = Set.new
+ @used = Set.new
+ @closed = false
+ @flashes = {}
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
def []=(k, v) #:nodoc:
+ raise ClosedError, :flash if closed?
keep(k)
- super
+ @flashes[k] = v
+ end
+
+ def [](k)
+ @flashes[k]
end
def update(h) #:nodoc:
h.keys.each { |k| keep(k) }
- super
+ @flashes.update h
+ self
+ end
+
+ def keys
+ @flashes.keys
+ end
+
+ def key?(name)
+ @flashes.key? name
+ end
+
+ def delete(key)
+ @flashes.delete key
+ self
+ end
+
+ def to_hash
+ @flashes.dup
+ end
+
+ def empty?
+ @flashes.empty?
+ end
+
+ def clear
+ @flashes.clear
+ end
+
+ def each(&block)
+ @flashes.each(&block)
end
alias :merge! :update
def replace(h) #:nodoc:
@used = Set.new
- super
+ @flashes.replace h
+ self
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -100,7 +149,7 @@ module ActionDispatch
#
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
def now
- FlashNow.new(self)
+ @now ||= FlashNow.new(self)
end
# Keeps either the entire current flash or a specific flash entry available for the next action:
@@ -184,8 +233,11 @@ module ActionDispatch
session = env['rack.session'] || {}
flash_hash = env['action_dispatch.request.flash_hash']
- if flash_hash && (!flash_hash.empty? || session.key?('flash'))
+ if flash_hash
+ if !flash_hash.empty? || session.key?('flash')
session["flash"] = flash_hash
+ end
+ flash_hash.close!
end
if session.key?('flash') && session['flash'].empty?
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 64d3a87fd0..1a811ce1b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -29,7 +29,9 @@ module ActionDispatch
end
def generate_sid
- ActiveSupport::SecureRandom.hex(16)
+ sid = ActiveSupport::SecureRandom.hex(16)
+ sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c57f694c4d..348f7b86b8 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -2,25 +2,23 @@ require 'rack/utils'
module ActionDispatch
class FileHandler
- def initialize(at, root)
- @at, @root = at.chomp('/'), root.chomp('/')
- @compiled_at = @at.blank? ? nil : /^#{Regexp.escape(at)}/
+ def initialize(root)
+ @root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
@file_server = ::Rack::File.new(@root)
end
def match?(path)
path = path.dup
- if !@compiled_at || path.sub!(@compiled_at, '')
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
- paths = "#{full_path}#{ext}"
- matches = Dir[paths]
- match = matches.detect { |m| File.file?(m) }
- if match
- match.sub!(@compiled_root, '')
- match
- end
+ full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ paths = "#{full_path}#{ext}"
+
+ matches = Dir[paths]
+ match = matches.detect { |m| File.file?(m) }
+ if match
+ match.sub!(@compiled_root, '')
+ match
end
end
@@ -39,9 +37,9 @@ module ActionDispatch
class Static
FILE_METHODS = %w(GET HEAD).freeze
- def initialize(app, roots)
+ def initialize(app, path)
@app = app
- @file_handlers = create_file_handlers(roots)
+ @file_handler = FileHandler.new(path)
end
def call(env)
@@ -49,24 +47,13 @@ module ActionDispatch
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
- @file_handlers.each do |file_handler|
- if match = file_handler.match?(path)
- env["PATH_INFO"] = match
- return file_handler.call(env)
- end
+ if match = @file_handler.match?(path)
+ env["PATH_INFO"] = match
+ return @file_handler.call(env)
end
end
@app.call(env)
end
-
- private
- def create_file_handlers(roots)
- roots = { '' => roots } unless roots.is_a?(Hash)
-
- roots.map do |at, root|
- FileHandler.new(at, root) if File.exist?(root)
- end.compact
- end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 35be0b3a27..a65f6e1fce 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -1345,11 +1346,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- [:resource, :resources].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:resource, :resources])
end
def resource_method_scope? #:nodoc:
- [:collection, :member, :new].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:collection, :member, :new])
end
def with_exclusive_scope
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 77a15f3e97..8a04cfa886 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -33,7 +35,7 @@ module ActionDispatch
def assert_response(type, message = nil)
validate_request!
- if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
+ if type.in?([:success, :missing, :redirect, :error]) && @response.send("#{type}?")
assert_block("") { true } # to count the assertion
elsif type.is_a?(Fixnum) && @response.response_code == type
assert_block("") { true } # to count the assertion
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 2b862fb7d6..c67a0664dc 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -1,4 +1,5 @@
require 'action_controller/vendor/html-scanner'
+require 'active_support/core_ext/object/inclusion'
#--
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
@@ -18,7 +19,7 @@ module ActionDispatch
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
- # * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
+ #
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
@@ -79,7 +80,7 @@ module ActionDispatch
return matches
else
- root = response_from_page_or_rjs
+ root = response_from_page
end
case arg
@@ -203,7 +204,7 @@ module ActionDispatch
root.children.concat @selected
else
# Otherwise just operate on the response document.
- root = response_from_page_or_rjs
+ root = response_from_page
end
# First or second argument is the selector: string and we pass
@@ -325,144 +326,6 @@ module ActionDispatch
end
end
- # Selects content from the RJS response.
- #
- # === Narrowing down
- #
- # With no arguments, asserts that one or more elements are updated or
- # inserted by RJS statements.
- #
- # Use the +id+ argument to narrow down the assertion to only statements
- # that update or insert an element with that identifier.
- #
- # Use the first argument to narrow down assertions to only statements
- # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
- # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>,
- # <tt>:insert_html</tt> and <tt>:redirect</tt>.
- #
- # Use the argument <tt>:insert</tt> followed by an insertion position to narrow
- # down the assertion to only statements that insert elements in that
- # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
- # and <tt>:after</tt>.
- #
- # Use the argument <tt>:redirect</tt> followed by a path to check that an statement
- # which redirects to the specified path is generated.
- #
- # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
- # be ignored as there is no HTML passed for this statement.
- #
- # === Using blocks
- #
- # Without a block, +assert_select_rjs+ merely asserts that the response
- # contains one or more RJS statements that replace or update content.
- #
- # With a block, +assert_select_rjs+ also selects all elements used in
- # these statements and passes them to the block. Nested assertions are
- # supported.
- #
- # Calling +assert_select_rjs+ with no arguments and using nested asserts
- # asserts that the HTML content is returned by one or more RJS statements.
- # Using +assert_select+ directly makes the same assertion on the content,
- # but without distinguishing whether the content is returned in an HTML
- # or JavaScript.
- #
- # ==== Examples
- #
- # # Replacing the element foo.
- # # page.replace 'foo', ...
- # assert_select_rjs :replace, "foo"
- #
- # # Replacing with the chained RJS proxy.
- # # page[:foo].replace ...
- # assert_select_rjs :chained_replace, 'foo'
- #
- # # Inserting into the element bar, top position.
- # assert_select_rjs :insert, :top, "bar"
- #
- # # Remove the element bar
- # assert_select_rjs :remove, "bar"
- #
- # # Changing the element foo, with an image.
- # assert_select_rjs "foo" do
- # assert_select "img[src=/images/logo.gif""
- # end
- #
- # # RJS inserts or updates a list with four items.
- # assert_select_rjs do
- # assert_select "ol>li", 4
- # end
- #
- # # The same, but shorter.
- # assert_select "ol>li", 4
- #
- # # Checking for a redirect.
- # assert_select_rjs :redirect, root_path
- def assert_select_rjs(*args, &block)
- rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
- id = args.first.is_a?(String) ? args.shift : nil
-
- # If the first argument is a symbol, it's the type of RJS statement we're looking
- # for (update, replace, insertion, etc). Otherwise, we're looking for just about
- # any RJS statement.
- if rjs_type
- if rjs_type == :insert
- position = args.shift
- id = args.shift
- insertion = "insert_#{position}".to_sym
- raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
- statement = "(#{RJS_STATEMENTS[insertion]})"
- else
- raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
- statement = "(#{RJS_STATEMENTS[rjs_type]})"
- end
- else
- statement = "#{RJS_STATEMENTS[:any]}"
- end
-
- # Next argument we're looking for is the element identifier. If missing, we pick
- # any element, otherwise we replace it in the statement.
- pattern = Regexp.new(
- id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
- )
-
- # Duplicate the body since the next step involves destroying it.
- matches = nil
- case rjs_type
- when :remove, :show, :hide, :toggle
- matches = @response.body.match(pattern)
- else
- @response.body.gsub(pattern) do |match|
- html = unescape_rjs(match)
- matches ||= []
- matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
- ""
- end
- end
-
- if matches
- assert_block("") { true } # to count the assertion
- if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
- begin
- @selected ||= nil
- in_scope, @selected = @selected, matches
- yield matches
- ensure
- @selected = in_scope
- end
- end
- matches
- else
- # RJS statement not found.
- case rjs_type
- when :remove, :show, :hide, :toggle
- flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
- else
- flunk_message = "No RJS statement that replaces or inserts HTML content."
- end
- flunk args.shift || flunk_message
- end
- end
-
# Extracts the content of an element, treats it as encoded HTML and runs
# nested assertion on it.
#
@@ -562,62 +425,9 @@ module ActionDispatch
end
protected
- RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
- RJS_ANY_ID = "\"([^\"])*\""
- RJS_STATEMENTS = {
- :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
- :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
- :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :redirect => "window.location.href = #{RJS_ANY_ID}"
- }
- [:remove, :show, :hide, :toggle].each do |action|
- RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
- end
- RJS_INSERTIONS = ["top", "bottom", "before", "after"]
- RJS_INSERTIONS.each do |insertion|
- RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
- end
- RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
- RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
- RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
-
- # +assert_select+ and +css_select+ call this to obtain the content in the HTML
- # page, or from all the RJS statements, depending on the type of response.
- def response_from_page_or_rjs()
- content_type = @response.content_type
-
- if content_type && Mime::JS =~ content_type
- body = @response.body.dup
- root = HTML::Node.new(nil)
-
- while true
- next if body.sub!(RJS_STATEMENTS[:any]) do |match|
- html = unescape_rjs(match)
- matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
- root.children.concat matches
- ""
- end
- break
- end
-
- root
- else
- html_document.root
- end
- end
-
- # Unescapes a RJS string.
- def unescape_rjs(rjs_string)
- # RJS encodes double quotes and line breaks.
- unescaped= rjs_string.gsub('\"', '"')
- unescaped.gsub!(/\\\//, '/')
- unescaped.gsub!('\n', "\n")
- unescaped.gsub!('\076', '>')
- unescaped.gsub!('\074', '<')
- # RJS encodes non-ascii characters.
- unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
- unescaped
+ # +assert_select+ and +css_select+ call this to obtain the content in the HTML page.
+ def response_from_page
+ html_document.root
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 5c6416a19e..7d707d03a9 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,6 +1,7 @@
require 'stringio'
require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/try'
require 'rack/test'
require 'test/unit/assertions'
@@ -26,31 +27,31 @@ module ActionDispatch
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
- # +put+, +delete+, and +head+.
+ # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+,
+ # +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
- # Performs a POST request with the given parameters. See get() for more
+ # Performs a POST request with the given parameters. See +#get+ for more
# details.
def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
- # Performs a PUT request with the given parameters. See get() for more
+ # Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
process :put, path, parameters, headers
end
- # Performs a DELETE request with the given parameters. See get() for
+ # Performs a DELETE request with the given parameters. See +#get+ for
# more details.
def delete(path, parameters = nil, headers = nil)
process :delete, path, parameters, headers
end
- # Performs a HEAD request with the given parameters. See get() for more
+ # Performs a HEAD request with the given parameters. See +#get+ for more
# details.
def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
@@ -59,7 +60,7 @@ module ActionDispatch
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is :get, :post, :put, :delete or :head; the
+ # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
# parameters are +nil+, a hash, or a url-encoded or multipart string;
# the headers are a hash. Keys are automatically upcased and prefixed
# with 'HTTP_' if not already.
@@ -243,7 +244,8 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_environment = nil)
+ def process(method, path, parameters = nil, env = nil)
+ env ||= {}
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -259,7 +261,7 @@ module ActionDispatch
hostname, port = host.split(':')
- env = {
+ default_env = {
:method => method,
:params => parameters,
@@ -277,9 +279,7 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
- (rack_environment || {}).each do |key, value|
- env[key] = value
- end
+ env.reverse_merge!(default_env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
@@ -321,7 +321,7 @@ module ActionDispatch
define_method(method) do |*args|
reset! unless integration_session
# reset the html_document variable, but only for new get/post calls
- @html_document = nil unless %w(cookies assigns).include?(method)
+ @html_document = nil unless method.in?(["cookies", "assigns"])
integration_session.__send__(method, *args).tap do
copy_session_variables!
end
@@ -384,7 +384,7 @@ module ActionDispatch
end
end
- # An test that spans multiple controllers and actions,
+ # An integration test spans multiple controllers and actions,
# tying them all together to ensure they work together as expected. It tests
# more completely than either unit or functional tests do, exercising the
# entire stack, from the dispatcher to the database.
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 60665387b6..4547aceb28 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -44,6 +44,7 @@ module ActionView
autoload :AbstractRenderer
autoload :PartialRenderer
autoload :TemplateRenderer
+ autoload :StreamingTemplateRenderer
end
autoload_at "action_view/template/resolver" do
@@ -53,6 +54,16 @@ module ActionView
autoload :FallbackFileSystemResolver
end
+ autoload_at "action_view/buffers" do
+ autoload :OutputBuffer
+ autoload :StreamingBuffer
+ end
+
+ autoload_at "action_view/flows" do
+ autoload :OutputFlow
+ autoload :StreamingFlow
+ end
+
autoload_at "action_view/template/error" do
autoload :MissingTemplate
autoload :ActionViewError
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index ab8c6259c5..9e8a3c51a3 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -8,13 +8,12 @@ require 'action_view/log_subscriber'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
+ # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
- # If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
- # == ERb
+ # == ERB
#
- # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
+ # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
#
# <b>Names of all the people</b>
@@ -23,7 +22,7 @@ module ActionView #:nodoc:
# <% end %>
#
# The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
- # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
+ # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
#
# <%# WRONG %>
# Hi, Mr. <% puts "Frodo" %>
@@ -81,7 +80,7 @@ module ActionView #:nodoc:
#
# == Builder
#
- # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
+ # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
#
# Here are some basic examples:
@@ -131,37 +130,9 @@ module ActionView #:nodoc:
# end
#
# More builder documentation can be found at http://builder.rubyforge.org.
- #
- # == JavaScriptGenerator
- #
- # JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
- # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
- # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
- # and make updates to the page where the request originated from.
- #
- # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
- #
- # When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
- #
- # link_to_remote :url => {:action => 'delete'}
- #
- # The subsequently rendered <tt>delete.rjs</tt> might look like:
- #
- # page.replace_html 'sidebar', :partial => 'sidebar'
- # page.remove "person-#{@person.id}"
- # page.visual_effect :highlight, 'user-list'
- #
- # This refreshes the sidebar, removes a person element and highlights the user list.
- #
- # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.
class Base
include Helpers, Rendering, Partials, ::ERB::Util, Context
- # Specify whether RJS responses should be wrapped in a try/catch block
- # that alert()s the caught exception (and then re-raises it).
- cattr_accessor :debug_rjs
- @@debug_rjs = false
-
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
@@ -182,7 +153,7 @@ module ActionView #:nodoc:
end
end
- attr_accessor :_template
+ attr_accessor :_template, :_view_flow
attr_internal :request, :controller, :config, :assigns, :lookup_context
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
@@ -210,8 +181,8 @@ module ActionView #:nodoc:
self.helpers = Module.new unless self.class.helpers
@_config = {}
- @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_virtual_path = nil
+ @_view_flow = OutputFlow.new
@output_buffer = nil
if @_controller = controller
@@ -224,10 +195,6 @@ module ActionView #:nodoc:
@_lookup_context.formats = formats if formats
end
- def store_content_for(key, value)
- @_content_for[key] = value
- end
-
def controller_path
@controller_path ||= controller && controller.controller_path
end
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
new file mode 100644
index 0000000000..089fc68706
--- /dev/null
+++ b/actionpack/lib/action_view/buffers.rb
@@ -0,0 +1,43 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
+ def initialize(*)
+ super
+ encode! if encoding_aware?
+ end
+
+ def <<(value)
+ super(value.to_s)
+ end
+ alias :append= :<<
+ alias :safe_append= :safe_concat
+ end
+
+ class StreamingBuffer #:nodoc:
+ def initialize(block)
+ @block = block
+ end
+
+ def <<(value)
+ value = value.to_s
+ value = ERB::Util.h(value) unless value.html_safe?
+ @block.call(value)
+ end
+ alias :concat :<<
+ alias :append= :<<
+
+ def safe_concat(value)
+ @block.call(value.to_s)
+ end
+ alias :safe_append= :safe_concat
+
+ def html_safe?
+ true
+ end
+
+ def html_safe
+ self
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
new file mode 100644
index 0000000000..386a06511f
--- /dev/null
+++ b/actionpack/lib/action_view/flows.rb
@@ -0,0 +1,79 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputFlow #:nodoc:
+ attr_reader :content
+
+ def initialize
+ @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
+ end
+
+ # Called by _layout_for to read stored values.
+ def get(key)
+ @content[key]
+ end
+
+ # Called by each renderer object to set the layout contents.
+ def set(key, value)
+ @content[key] = value
+ end
+
+ # Called by content_for
+ def append(key, value)
+ @content[key] << value
+ end
+
+ # Called by provide
+ def append!(key, value)
+ @content[key] << value
+ end
+ end
+
+ class StreamingFlow < OutputFlow #:nodoc:
+ def initialize(view, fiber)
+ @view = view
+ @parent = nil
+ @child = view.output_buffer
+ @content = view._view_flow.content
+ @fiber = fiber
+ @root = Fiber.current.object_id
+ end
+
+ # Try to get an stored content. If the content
+ # is not available and we are inside the layout
+ # fiber, we set that we are waiting for the given
+ # key and yield.
+ def get(key)
+ return super if @content.key?(key)
+
+ if inside_fiber?
+ view = @view
+
+ begin
+ @waiting_for = key
+ view.output_buffer, @parent = @child, view.output_buffer
+ Fiber.yield
+ ensure
+ @waiting_for = nil
+ view.output_buffer, @child = @parent, view.output_buffer
+ end
+ end
+
+ super
+ end
+
+ # Appends the contents for the given key. This is called
+ # by provides and resumes back to the fiber if it is
+ # the key it is waiting for.
+ def append!(key, value)
+ super
+ @fiber.resume if @waiting_for == key
+ end
+
+ private
+
+ def inside_fiber?
+ Fiber.current.object_id != @root
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index d338ce616a..205116f610 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -17,11 +17,10 @@ module ActionView #:nodoc:
autoload :FormTagHelper
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
- autoload :PrototypeHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
- autoload :ScriptaculousHelper
+ autoload :SprocketsHelper
autoload :TagHelper
autoload :TextHelper
autoload :TranslationHelper
@@ -47,11 +46,10 @@ module ActionView #:nodoc:
include FormTagHelper
include JavaScriptHelper
include NumberHelper
- include PrototypeHelper
include OutputSafetyHelper
include RecordTagHelper
include SanitizeHelper
- include ScriptaculousHelper
+ include SprocketsHelper
include TagHelper
include TextHelper
include TranslationHelper
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index 014a03c54d..1e00fd996b 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -30,9 +30,6 @@ module ActionView
source = rewrite_extension(source, dir, ext) if ext
source = "/#{dir}/#{source}" unless source[0] == ?/
- if controller.respond_to?(:env) && controller.env["action_dispatch.asset_path"]
- source = rewrite_asset_path(source, controller.env["action_dispatch.asset_path"])
- end
source = rewrite_asset_path(source, config.asset_path)
has_request = controller.respond_to?(:request)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 82bbfcc7d2..ce5a7dc2e5 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -86,99 +86,119 @@ module ActionView
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', 'js')
+ if config.use_sprockets
+ sprockets_javascript_path(source)
+ else
+ asset_paths.compute_public_path(source, 'javascripts', 'js')
+ end
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- # Returns an HTML script tag for each of the +sources+ provided. You
- # can pass in the filename (.js extension is optional) of JavaScript files
- # that exist in your <tt>public/javascripts</tt> directory for inclusion into the
- # current page or you can pass the full path relative to your document
- # root. To include the Prototype and Scriptaculous JavaScript libraries in
- # your application, pass <tt>:defaults</tt> as the source. When using
- # <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
- # <tt>public/javascripts</tt> it will be included as well. You can modify the
- # HTML attributes of the script tag by passing a hash as the last argument.
+ # Returns an HTML script tag for each of the +sources+ provided.
+ #
+ # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
+ # to <tt>public/javascripts</tt>, full paths are assumed to be relative to the document
+ # root. Relative paths are idiomatic, use absolute paths only when needed.
+ #
+ # When passing paths, the ".js" extension is optional.
+ #
+ # To include the default JavaScript expansion pass <tt>:defaults</tt> as source.
+ # By default, <tt>:defaults</tt> loads jQuery. If the application was generated
+ # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead.
+ # In any case, the defaults can be overridden in <tt>config/application.rb</tt>:
+ #
+ # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
+ #
+ # When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
+ # <tt>public/javascripts</tt> it will be included as well at the end.
+ #
+ # You can modify the HTML attributes of the script tag by passing a hash as the
+ # last argument.
#
# ==== Examples
- # javascript_include_tag "xmlhr" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "xmlhr.js" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr.js"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
- # <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
- # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
+ # javascript_include_tag "common.javascript", "/elsewhere/cools"
+ # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
+ # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr"
+ # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr.js"
+ # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag :defaults # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # javascript_include_tag :defaults
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
#
# * = The application.js file is only referenced if it exists
#
- # You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source:
+ # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source:
#
- # javascript_include_tag :all # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ # javascript_include_tag :all
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
- # all subsequently included files.
+ # Note that your defaults of choice will be included first, so they will be available to all subsequently
+ # included files.
#
- # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
+ # If you want Rails to search in all the subdirectories under <tt>public/javascripts</tt>, you should
+ # explicitly set <tt>:recursive</tt>:
#
# javascript_include_tag :all, :recursive => true
#
- # == Caching multiple javascripts into one
+ # == Caching multiple JavaScripts into one
#
- # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
- # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
- # environment).
+ # You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download
+ # and can better be compressed by gzip (leading to faster transfers). Caching will only happen if
+ # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails
+ # production environment, but not for the development environment).
#
# ==== Examples
- # javascript_include_tag :all, :cache => true # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # javascript_include_tag :all, :cache => true # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ #
+ # # assuming config.perform_caching is true
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
+ # # assuming config.perform_caching is true
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
- @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
- @javascript_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_javascript_include_tag(*sources)
+ else
+ @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
+ @javascript_include.include_tag(*sources)
+ end
end
-
end
-
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index a48c87b49a..a994afb65e 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -63,7 +63,11 @@ module ActionView
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style
# stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ if config.use_sprockets
+ sprockets_stylesheet_path(source)
+ else
+ asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ end
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -136,8 +140,12 @@ module ActionView
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
- @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
- @stylesheet_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_stylesheet_link_tag(*sources)
+ else
+ @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
+ @stylesheet_include.include_tag(*sources)
+ end
end
end
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index db9d7a08ff..96e5722252 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -4,7 +4,7 @@ module ActionView
# = Action View Atom Feed Helpers
module Helpers #:nodoc:
module AtomFeedHelper
- # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
# template languages).
#
# Full usage example:
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index c88bd1efd5..0139714240 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -14,7 +14,7 @@ module ActionView
# variable. You can then use this variable anywhere in your templates or layout.
#
# ==== Examples
- # The capture method can be used in ERb templates...
+ # The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
@@ -107,8 +107,8 @@ module ActionView
# <%= javascript_include_tag :defaults %>
# <% end %>
#
- # That will place <tt>script</tt> tags for Prototype, Scriptaculous, and application.js (if it exists)
- # on the page; this technique is useful if you'll only be using these scripts in a few views.
+ # That will place +script+ tags for your default set of JavaScript files on the page;
+ # this technique is useful if you'll only be using these scripts in a few views.
#
# Note that content_for concatenates the blocks it is given for a particular
# identifier in order. For example:
@@ -135,8 +135,19 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- @_content_for[name] << content if content
- @_content_for[name] unless content
+ result = @_view_flow.append(name, content) if content
+ result unless content
+ end
+
+ # The same as +content_for+ but when used with streaming flushes
+ # straight back to the layout. In other words, if you want to
+ # concatenate several times to the same buffer when rendering a given
+ # template, you should use +content_for+, if not, use +provide+ to tell
+ # the layout to stop looking for more contents.
+ def provide(name, content = nil, &block)
+ content = capture(&block) if block_given?
+ result = @_view_flow.append!(name, content) if content
+ result unless content
end
# content_for? simply checks whether any content has been captured yet using content_for
@@ -158,7 +169,7 @@ module ActionView
# </body>
# </html>
def content_for?(name)
- @_content_for[name].present?
+ @_view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 65c8debc76..1f2bc28cac 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -17,10 +17,12 @@ module ActionView
# Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
# so they do not use these tags.
def csrf_meta_tags
- <<-METAS.strip_heredoc.chomp.html_safe if protect_against_forgery?
- <meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>
- <meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>
- METAS
+ if protect_against_forgery?
+ [
+ tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
+ tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
+ ].join("\n").html_safe
+ end
end
# For backwards compatibility.
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6cd1565031..313a2591bf 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -26,9 +26,9 @@ module ActionView
# 30 secs <-> 1 min, 29 secs # => 1 minute
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
- # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
- # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
- # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
+ # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
+ # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
+ # 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
@@ -89,8 +89,8 @@ module ActionView
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2529 then locale.t :x_days, :count => 1
- when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ when 1440..2519 then locale.t :x_days, :count => 1
+ when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 9025d9e24c..440acafa88 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -610,10 +610,11 @@ module ActionView
# label(:post, :body)
# # => <label for="post_body">Write your entire text here</label>
#
- # Localization can also be based purely on the translation of the attribute-name like this:
+ # Localization can also be based purely on the translation of the attribute-name
+ # (if you are using ActiveRecord):
#
- # activemodel:
- # attribute:
+ # activerecord:
+ # attributes:
# post:
# cost: "Total cost"
#
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index a19ba7a968..d7228bab67 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,42 +1,8 @@
require 'action_view/helpers/tag_helper'
module ActionView
- # = Action View JavaScript Helpers
module Helpers
- # Provides functionality for working with JavaScript in your views.
- #
- # == Ajax, controls and visual effects
- #
- # * For information on using Ajax, see
- # ActionView::Helpers::PrototypeHelper.
- # * For information on using controls and visual effects, see
- # ActionView::Helpers::ScriptaculousHelper.
- #
- # == Including the JavaScript libraries into your pages
- #
- # Rails includes the Prototype JavaScript framework and the Scriptaculous
- # JavaScript controls and visual effects library. If you wish to use
- # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
- # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
- # following:
- #
- # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
- # section of your page (recommended): This function will return
- # references to the JavaScript files created by the +rails+ command in
- # your <tt>public/javascripts</tt> directory. Using it is recommended as
- # the browser can then cache the libraries instead of fetching all the
- # functions anew on every request.
- # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
- # will only include the Prototype core library, which means you are able
- # to use all basic AJAX functionality. For the Scriptaculous-based
- # JavaScript helpers, like visual effects, autocompletion, drag and drop
- # and so on, you should use the method described above.
- #
- # For documentation on +javascript_include_tag+ see
- # ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
- include PrototypeHelper
-
JS_ESCAPE_MAP = {
'\\' => '\\\\',
'</' => '<\/',
@@ -96,87 +62,34 @@ module ActionView
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
- # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
- # onclick handler.
- #
- # The first argument +name+ is used as the button's value or display text.
- #
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # Returns a button whose +onclick+ handler triggers the passed JavaScript.
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as button label and the JavaScript code goes into its +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
+ # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
#
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- # Examples:
- # button_to_function "Greeting", "alert('Hello world!')"
- # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
- # button_to_function "Details" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- # button_to_function "Details", :class => "details_button" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- def button_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def button_to_function(name, function=nil, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
- # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
- # onclick handler and return false after the fact.
- #
- # The first argument +name+ is used as the link text.
+ # Returns a link whose +onclick+ handler triggers the passed JavaScript.
#
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
+ # the JavaScript is set, the helper appends "; return false;".
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # The +href+ attribute of the tag is set to "#" unles +html_options+ has one.
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
+ # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
#
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- #
- # Examples:
- # link_to_function "Greeting", "alert('Hello world!')"
- # Produces:
- # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
- #
- # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
- # Produces:
- # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
- # <img src="/images/delete.png?" alt="Delete"/>
- # </a>
- #
- # link_to_function("Show me more", nil, :id => "more_link") do |page|
- # page[:details].visual_effect :toggle_blind
- # page[:more_link].replace_html "Show me less"
- # end
- # Produces:
- # <a href="#" id="more_link" onclick="try {
- # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
- # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
- # }
- # catch (e) {
- # alert('RJS error:\n\n' + e.toString());
- # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
- # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
- # throw e
- # };
- # return false;">Show me more</a>
- #
- def link_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def link_to_function(name, function, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
deleted file mode 100644
index 18e303778c..0000000000
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ /dev/null
@@ -1,852 +0,0 @@
-require 'set'
-require 'active_support/json'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View Prototype Helpers
- module Helpers
- # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
- # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
- # This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
- # injections into the DOM. A common use case is having a form that adds
- # a new element to a list without reloading the page or updating a shopping
- # cart total when a new item is added.
- #
- # == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
- #
- # javascript_include_tag 'prototype'
- #
- # (See the documentation for
- # ActionView::Helpers::JavaScriptHelper for more information on including
- # this and other JavaScript files in your Rails templates.)
- #
- # Now you're ready to call a remote action either through a link...
- #
- # link_to_remote "Add to cart",
- # :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
- #
- # ...through a form...
- #
- # <%= form_remote_tag :url => '/shipping' do -%>
- # <div><%= submit_tag 'Recalculate Shipping' %></div>
- # <% end -%>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
- # are listed here); check out the documentation for each method to find out more about its usage and options.
- #
- # === Common Options
- # See link_to_remote for documentation of options common to all Ajax
- # helpers; any of the options specified by link_to_remote can be used
- # by the other helpers.
- #
- # == Designing your Rails actions for Ajax
- # When building your action handlers (that is, the Rails actions that receive your background requests), it's
- # important to remember a few things. First, whatever your action would normally return to the browser, it will
- # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
- # You can turn the layout off on particular actions by doing the following:
- #
- # class SiteController < ActionController::Base
- # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
- # end
- #
- # Optionally, you could do this in the method you wish to lack a layout:
- #
- # render :layout => false
- #
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
- # def name
- # # Is this an XmlHttpRequest request?
- # if (request.xhr?)
- # render :text => @name.to_s
- # else
- # # No? Then render an action.
- # render :action => 'view_attribute', :attr => @name
- # end
- # end
- #
- # The else clause can be left off and the current action will render with full layout and template. An extension
- # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
- #
- # layout proc{ |c| c.request.xhr? ? false : "application" }
- #
- # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
- #
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
- # render text output, like this:
- #
- # render :text => 'Return this from my method!'
- #
- # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
- # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
- #
- # == Updating multiple elements
- # See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
- module PrototypeHelper
- CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
- :interactive, :complete, :failure, :success ] +
- (100..599).to_a)
- AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
- :asynchronous, :method, :insertion, :position,
- :form, :with, :update, :script, :type ]).merge(CALLBACKS)
-
- # Returns the JavaScript needed for a remote function.
- # See the link_to_remote documentation at https://github.com/rails/prototype_legacy_helper as it takes the same arguments.
- #
- # Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
- # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
- # :url => { :action => :update_options }) %>">
- # <option value="0">Hello</option>
- # <option value="1">World</option>
- # </select>
- def remote_function(options)
- javascript_options = options_for_ajax(options)
-
- update = ''
- if options[:update] && options[:update].is_a?(Hash)
- update = []
- update << "success:'#{options[:update][:success]}'" if options[:update][:success]
- update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
- update = '{' + update.join(',') + '}'
- elsif options[:update]
- update << "'#{options[:update]}'"
- end
-
- function = update.empty? ?
- "new Ajax.Request(" :
- "new Ajax.Updater(#{update}, "
-
- url_options = options[:url]
- function << "'#{ERB::Util.html_escape(escape_javascript(url_for(url_options)))}'"
- function << ", #{javascript_options})"
-
- function = "#{options[:before]}; #{function}" if options[:before]
- function = "#{function}; #{options[:after]}" if options[:after]
- function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
- function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
-
- return function.html_safe
- end
-
- # All the methods were moved to GeneratorMethods so that
- # #include_helpers_from_context has nothing to overwrite.
- class JavaScriptGenerator #:nodoc:
- def initialize(context, &block) #:nodoc:
- @context, @lines = context, []
- include_helpers_from_context
- @context.with_output_buffer(@lines) do
- @context.instance_exec(self, &block)
- end
- end
-
- private
- def include_helpers_from_context
- extend @context.helpers if @context.respond_to?(:helpers)
- extend GeneratorMethods
- end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
- # this in your Ajax response bodies, either in a <tt>\<script></tt> tag
- # or as plain JavaScript sent with a Content-type of "text/javascript".
- #
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
- #
- # Example:
- #
- # # Generates:
- # # new Element.insert("list", { bottom: "<li>Some item</li>" });
- # # new Effect.Highlight("list");
- # # ["status-indicator", "cancel-link"].each(Element.hide);
- # update_page do |page|
- # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
- # page.visual_effect :highlight, 'list'
- # page.hide 'status-indicator', 'cancel-link'
- # end
- #
- #
- # Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
- # object, that method will also have access to a +page+ object.
- #
- # Example:
- #
- # module ApplicationHelper
- # def update_time
- # page.replace_html 'time', Time.now.to_s(:db)
- # page.visual_effect :highlight, 'time'
- # end
- # end
- #
- # # Controller action
- # def poll
- # render(:update) { |page| page.update_time }
- # end
- #
- # Calls to JavaScriptGenerator not matching a helper method below
- # generate a proxy to the JavaScript Class named by the method called.
- #
- # Examples:
- #
- # # Generates:
- # # Foo.init();
- # update_page do |page|
- # page.foo.init
- # end
- #
- # # Generates:
- # # Event.observe('one', 'click', function () {
- # # $('two').show();
- # # });
- # update_page do |page|
- # page.event.observe('one', 'click') do |p|
- # p[:two].show
- # end
- # end
- #
- # You can also use PrototypeHelper#update_page_tag instead of
- # PrototypeHelper#update_page to wrap the generated JavaScript in a
- # <tt>\<script></tt> tag.
- module GeneratorMethods
- def to_s #:nodoc:
- (@lines * $/).tap do |javascript|
- if ActionView::Base.debug_rjs
- source = javascript.dup
- javascript.replace "try {\n#{source}\n} catch (e) "
- javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
- end
- end
- end
-
- # Returns a element reference by finding it through +id+ in the DOM. This element can then be
- # used for further method calls. Examples:
- #
- # page['blank_slate'] # => $('blank_slate');
- # page['blank_slate'].show # => $('blank_slate').show();
- # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
- #
- # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
- # the correct id:
- #
- # page[@post] # => $('post_45')
- # page[Post.new] # => $('new_post')
- def [](id)
- case id
- when String, Symbol, NilClass
- JavaScriptElementProxy.new(self, id)
- else
- JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
- end
- end
-
- # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
- # expression as an argument to another JavaScriptGenerator method.
- def literal(code)
- ::ActiveSupport::JSON::Variable.new(code.to_s)
- end
-
- # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
- # used for further method calls. Examples:
- #
- # page.select('p') # => $$('p');
- # page.select('p.welcome b').first # => $$('p.welcome b').first();
- # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
- # You can also use prototype enumerations with the collection. Observe:
- #
- # # Generates: $$('#items li').each(function(value) { value.hide(); });
- # page.select('#items li').each do |value|
- # value.hide
- # end
- #
- # Though you can call the block param anything you want, they are always rendered in the
- # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
- #
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
- # page.select('#items li').collect('hidden') do |item|
- # item.hide
- # end
- #
- def select(pattern)
- JavaScriptElementCollectionProxy.new(self, pattern)
- end
-
- # Inserts HTML at the specified +position+ relative to the DOM element
- # identified by the given +id+.
- #
- # +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
- # element's existing content.
- # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
- # element's existing content.
- # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
- # <tt>:after</tt>:: HTML is inserted immediately following the element.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Insert the rendered 'navigation' partial just before the DOM
- # # element with ID 'content'.
- # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
- # page.insert_html :before, 'content', :partial => 'navigation'
- #
- # # Add a list item to the bottom of the <ul> with ID 'list'.
- # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
- # page.insert_html :bottom, 'list', '<li>Last item</li>'
- #
- def insert_html(position, id, *options_for_render)
- content = javascript_object_for(render(*options_for_render))
- record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
- end
-
- # Replaces the inner HTML of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the HTML of the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
- # page.replace_html 'person-45', :partial => 'person', :object => @person
- #
- def replace_html(id, *options_for_render)
- call 'Element.update', id, render(*options_for_render)
- end
-
- # Replaces the "outer HTML" (i.e., the entire element, not just its
- # contents) of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # page.replace 'person-45', :partial => 'person', :object => @person
- #
- # This allows the same partial that is used for the +insert_html+ to
- # be also used for the input to +replace+ without resorting to
- # the use of wrapper elements.
- #
- # Examples:
- #
- # <div id="people">
- # <%= render :partial => 'person', :collection => @people %>
- # </div>
- #
- # # Insert a new person
- # #
- # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
- # page.insert_html :bottom, :partial => 'person', :object => @person
- #
- # # Replace an existing person
- #
- # # Generates: Element.replace("person_45", "-- Contents of partial --");
- # page.replace 'person_45', :partial => 'person', :object => @person
- #
- def replace(id, *options_for_render)
- call 'Element.replace', id, render(*options_for_render)
- end
-
- # Removes the DOM elements with the given +ids+ from the page.
- #
- # Example:
- #
- # # Remove a few people
- # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
- # page.remove 'person_23', 'person_9', 'person_2'
- #
- def remove(*ids)
- loop_on_multiple_args 'Element.remove', ids
- end
-
- # Shows hidden DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
- # page.show 'person_6', 'person_13', 'person_223'
- #
- def show(*ids)
- loop_on_multiple_args 'Element.show', ids
- end
-
- # Hides the visible DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Hide a few people
- # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
- # page.hide 'person_29', 'person_9', 'person_0'
- #
- def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
- end
-
- # Toggles the visibility of the DOM elements with the given +ids+.
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
- # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
- # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
- #
- def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
- end
-
- # Displays an alert dialog with the given +message+.
- #
- # Example:
- #
- # # Generates: alert('This message is from Rails!')
- # page.alert('This message is from Rails!')
- def alert(message)
- call 'alert', message
- end
-
- # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
- #
- # Examples:
- #
- # # Generates: window.location.href = "/mycontroller";
- # page.redirect_to(:action => 'index')
- #
- # # Generates: window.location.href = "/account/signup";
- # page.redirect_to(:controller => 'account', :action => 'signup')
- def redirect_to(location)
- url = location.is_a?(String) ? location : @context.url_for(location)
- record "window.location.href = #{url.inspect}"
- end
-
- # Reloads the browser's current +location+ using JavaScript
- #
- # Examples:
- #
- # # Generates: window.location.reload();
- # page.reload
- def reload
- record 'window.location.reload()'
- end
-
- # Calls the JavaScript +function+, optionally with the given +arguments+.
- #
- # If a block is given, the block will be passed to a new JavaScriptGenerator;
- # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
- # and passed as the called function's final argument.
- #
- # Examples:
- #
- # # Generates: Element.replace(my_element, "My content to replace with.")
- # page.call 'Element.replace', 'my_element', "My content to replace with."
- #
- # # Generates: alert('My message!')
- # page.call 'alert', 'My message!'
- #
- # # Generates:
- # # my_method(function() {
- # # $("one").show();
- # # $("two").hide();
- # # });
- # page.call(:my_method) do |p|
- # p[:one].show
- # p[:two].hide
- # end
- def call(function, *arguments, &block)
- record "#{function}(#{arguments_for_call(arguments, block)})"
- end
-
- # Assigns the JavaScript +variable+ the given +value+.
- #
- # Examples:
- #
- # # Generates: my_string = "This is mine!";
- # page.assign 'my_string', 'This is mine!'
- #
- # # Generates: record_count = 33;
- # page.assign 'record_count', 33
- #
- # # Generates: tabulated_total = 47
- # page.assign 'tabulated_total', @total_from_cart
- #
- def assign(variable, value)
- record "#{variable} = #{javascript_object_for(value)}"
- end
-
- # Writes raw JavaScript to the page.
- #
- # Example:
- #
- # page << "alert('JavaScript with Prototype.');"
- def <<(javascript)
- @lines << javascript
- end
-
- # Executes the content of the block after a delay of +seconds+. Example:
- #
- # # Generates:
- # # setTimeout(function() {
- # # ;
- # # new Effect.Fade("notice",{});
- # # }, 20000);
- # page.delay(20) do
- # page.visual_effect :fade, 'notice'
- # end
- def delay(seconds = 1)
- record "setTimeout(function() {\n\n"
- yield
- record "}, #{(seconds * 1000).to_i})"
- end
-
- private
- def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
- "#{method}(#{javascript_object_for(ids.first)})")
- end
-
- def page
- self
- end
-
- def record(line)
- line = "#{line.to_s.chomp.gsub(/\;\z/, '')};"
- self << line
- line
- end
-
- def render(*options)
- with_formats(:html) do
- case option = options.first
- when Hash
- @context.render(*options)
- else
- option.to_s
- end
- end
- end
-
- def with_formats(*args)
- @context ? @context.lookup_context.update_details(:formats => args) { yield } : yield
- end
-
- def javascript_object_for(object)
- ::ActiveSupport::JSON.encode(object)
- end
-
- def arguments_for_call(arguments, block = nil)
- arguments << block_to_function(block) if block
- arguments.map { |argument| javascript_object_for(argument) }.join ', '
- end
-
- def block_to_function(block)
- generator = self.class.new(@context, &block)
- literal("function() { #{generator.to_s} }")
- end
-
- def method_missing(method, *arguments)
- JavaScriptProxy.new(self, method.to_s.camelize)
- end
- end
- end
-
- # Yields a JavaScriptGenerator and returns the generated JavaScript code.
- # Use this to update multiple elements on a page in an Ajax response.
- # See JavaScriptGenerator for more information.
- #
- # Example:
- #
- # update_page do |page|
- # page.hide 'spinner'
- # end
- def update_page(&block)
- JavaScriptGenerator.new(self, &block).to_s.html_safe
- end
-
- # Works like update_page but wraps the generated JavaScript in a
- # <tt>\<script></tt> tag. Use this to include generated JavaScript in an
- # ERb template. See JavaScriptGenerator for more information.
- #
- # +html_options+ may be a hash of <tt>\<script></tt> attributes to be
- # passed to ActionView::Helpers::JavaScriptHelper#javascript_tag.
- def update_page_tag(html_options = {}, &block)
- javascript_tag update_page(&block), html_options
- end
-
- protected
- def options_for_javascript(options)
- if options.empty?
- '{}'
- else
- "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
- end
- end
-
- def options_for_ajax(options)
- js_options = build_callbacks(options)
-
- js_options['asynchronous'] = options[:type] != :synchronous
- js_options['method'] = method_option_to_s(options[:method]) if options[:method]
- js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
- js_options['evalScripts'] = options[:script].nil? || options[:script]
-
- if options[:form]
- js_options['parameters'] = 'Form.serialize(this)'
- elsif options[:submit]
- js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
- elsif options[:with]
- js_options['parameters'] = options[:with]
- end
-
- if protect_against_forgery? && !options[:form]
- if js_options['parameters']
- js_options['parameters'] << " + '&"
- else
- js_options['parameters'] = "'"
- end
- js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
- end
-
- options_for_javascript(js_options)
- end
-
- def method_option_to_s(method)
- (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
- end
-
- def build_callbacks(options)
- callbacks = {}
- options.each do |callback, code|
- if CALLBACKS.include?(callback)
- name = 'on' + callback.to_s.capitalize
- callbacks[name] = "function(request){#{code}}"
- end
- end
- callbacks
- end
- end
-
- # Converts chained method calls on DOM proxy elements into JavaScript chains
- class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
-
- def initialize(generator, root = nil)
- @generator = generator
- @generator << root if root
- end
-
- def is_a?(klass)
- klass == JavaScriptProxy
- end
-
- private
- def method_missing(method, *arguments, &block)
- if method.to_s =~ /(.*)=$/
- assign($1, arguments.first)
- else
- call("#{method.to_s.camelize(:lower)}", *arguments, &block)
- end
- end
-
- def call(function, *arguments, &block)
- append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
- self
- end
-
- def assign(variable, value)
- append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
- end
-
- def function_chain
- @function_chain ||= @generator.instance_variable_get(:@lines)
- end
-
- def append_to_function_chain!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call};"
- end
- end
-
- class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, id)
- @id = id
- super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
- end
-
- # Allows access of element attributes through +attribute+. Examples:
- #
- # page['foo']['style'] # => $('foo').style;
- # page['foo']['style']['color'] # => $('blank_slate').style.color;
- # page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
- # page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
- def [](attribute)
- append_to_function_chain!(attribute)
- self
- end
-
- def []=(variable, value)
- assign(variable, value)
- end
-
- def replace_html(*options_for_render)
- call 'update', @generator.send(:render, *options_for_render)
- end
-
- def replace(*options_for_render)
- call 'replace', @generator.send(:render, *options_for_render)
- end
-
- def reload(options_for_replace = {})
- replace(options_for_replace.merge({ :partial => @id.to_s }))
- end
-
- end
-
- class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, variable)
- @variable = ::ActiveSupport::JSON::Variable.new(variable)
- @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
- super(generator)
- end
-
- # The JSON Encoder calls this to check for the +to_json+ method
- # Since it's a blank slate object, I suppose it responds to anything.
- def respond_to?(*)
- true
- end
-
- def as_json(options = nil)
- @variable
- end
-
- private
- def append_to_function_chain!(call)
- @generator << @variable if @empty
- @empty = false
- super
- end
- end
-
- class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
- ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] unless defined? ENUMERABLE_METHODS_WITH_RETURN
- ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
- attr_reader :generator
- delegate :arguments_for_call, :to => :generator
-
- def initialize(generator, pattern)
- super(generator, @pattern = pattern)
- end
-
- def each_slice(variable, number, &block)
- if block
- enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
- else
- add_variable_assignment!(variable)
- append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
- end
- end
-
- def grep(variable, pattern, &block)
- enumerate :grep, :variable => variable, :return => true, :method_args => [::ActiveSupport::JSON::Variable.new(pattern.inspect)], :yield_args => %w(value index), &block
- end
-
- def in_groups_of(variable, number, fill_with = nil)
- arguments = [number]
- arguments << fill_with unless fill_with.nil?
- add_variable_assignment!(variable)
- append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
- def inject(variable, memo, &block)
- enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
- end
-
- def pluck(variable, property)
- add_variable_assignment!(variable)
- append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
- end
-
- def zip(variable, *arguments, &block)
- add_variable_assignment!(variable)
- append_enumerable_function!("zip(#{arguments_for_call arguments}")
- if block
- function_chain[-1] += ", function(array) {"
- yield ::ActiveSupport::JSON::Variable.new('array')
- add_return_statement!
- @generator << '});'
- else
- function_chain[-1] += ');'
- end
- end
-
- private
- def method_missing(method, *arguments, &block)
- if ENUMERABLE_METHODS.include?(method)
- returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
- variable = arguments.first if returnable
- enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
- else
- super
- end
- end
-
- # Options
- # * variable - name of the variable to set the result of the enumeration to
- # * method_args - array of the javascript enumeration method args that occur before the function
- # * yield_args - array of the javascript yield args
- # * return - true if the enumeration should return the last statement
- def enumerate(enumerable, options = {}, &block)
- options[:method_args] ||= []
- options[:yield_args] ||= []
- yield_args = options[:yield_args] * ', '
- method_args = arguments_for_call options[:method_args] # foo, bar, function
- method_args << ', ' unless method_args.blank?
- add_variable_assignment!(options[:variable]) if options[:variable]
- append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
- # only yield as many params as were passed in the block
- yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
- add_return_statement! if options[:return]
- @generator << '});'
- end
-
- def add_variable_assignment!(variable)
- function_chain.push("var #{variable} = #{function_chain.pop}")
- end
-
- def add_return_statement!
- unless function_chain.last =~ /return/
- function_chain.push("return #{function_chain.pop.chomp(';')};")
- end
- end
-
- def append_enumerable_function!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call}"
- end
- end
-
- class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
- def initialize(generator, pattern)
- super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
deleted file mode 100644
index 8610c2469e..0000000000
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-require 'action_view/helpers/javascript_helper'
-require 'active_support/json'
-
-module ActionView
- # = Action View Scriptaculous Helpers
- module Helpers
- # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/]
- # JavaScript functions, including those which create Ajax controls and visual
- # effects.
- #
- # To be able to use these helpers, you must include the Prototype
- # JavaScript framework and the Scriptaculous JavaScript library in your
- # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
- # for more information on including the necessary JavaScript.
- #
- # The Scriptaculous helpers' behavior can be tweaked with various options.
- #
- # See the documentation at http://script.aculo.us for more information on
- # using these helpers in your application.
- module ScriptaculousHelper
- TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
-
- # Returns a JavaScript snippet to be used on the Ajax callbacks for
- # starting visual effects.
- #
- # If no +element_id+ is given, it assumes "element" which should be a local
- # variable in the generated JavaScript execution context. This can be
- # used for example with +drop_receiving_element+:
- #
- # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
- #
- # This would fade the element that was dropped on the drop receiving
- # element.
- #
- # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
- # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
- # blinddown/blindup respectively.
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def visual_effect(name, element_id = false, js_options = {})
- element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
-
- js_options[:queue] = if js_options[:queue].is_a?(Hash)
- '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
- elsif js_options[:queue]
- "'#{js_options[:queue]}'"
- end if js_options[:queue]
-
- [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
- js_options[option] = "'#{js_options[option]}'" if js_options[option]
- end
-
- if TOGGLE_EFFECTS.include? name.to_sym
- "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
- else
- "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
- end
- end
-
- # Makes the element with the DOM ID specified by +element_id+ sortable
- # by drag-and-drop and make an Ajax call whenever the sort order has
- # changed. By default, the action called gets the serialized sortable
- # element as parameters.
- #
- # Example:
- #
- # <%= sortable_element("my_list", :url => { :action => "order" }) %>
- #
- # In the example, the action gets a "my_list" array parameter
- # containing the values of the ids of elements the sortable consists
- # of, in the current order.
- #
- # Important: For this to work, the sortable elements must have id
- # attributes in the form "string_identifier". For example, "item_1". Only
- # the identifier part of the id attribute will be serialized.
- #
- # Additional +options+ are:
- #
- # * <tt>:format</tt> - A regular expression to determine what to send as the
- # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
- #
- # * <tt>:constraint</tt> - Whether to constrain the dragging to either
- # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
- #
- # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
- # or <tt>:vertical</tt> direction.
- #
- # * <tt>:tag</tt> - Which children of the container element to treat as
- # sortable (default is <tt>li</tt>).
- #
- # * <tt>:containment</tt> - Takes an element or array of elements to treat as
- # potential drop targets (defaults to the original target element).
- #
- # * <tt>:only</tt> - A CSS class name or array of class names used to filter
- # out child elements as candidates.
- #
- # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
- # operations if the list runs past the visual border.
- #
- # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
- # main sortable list. This means that you can create multi-layer lists,
- # and not only sort items at the same level, but drag and sort items
- # between levels.
- #
- # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
- # when an accepted Draggable is hovered over it.
- #
- # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
- # embedded handle. The value may be a string referencing a CSS class value
- # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
- # found within the element that has this CSS class value will be used as
- # the handle.
- #
- # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
- # the original in place until the clone is dropped (default is <tt>false</tt>).
- #
- # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
- # a Droppable, that can receive a Draggable (as according to the containment
- # rules) as a child element when there are no more elements inside (default
- # is <tt>false</tt>).
- #
- # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
- # dragging from one Sortable to another, the callback is called once on each
- # Sortable. Gets the affected element as its parameter.
- #
- # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
- # changed in any way. When dragging from one Sortable to another, the callback
- # is called once on each Sortable. Gets the container as its parameter.
- #
- # See http://script.aculo.us for more documentation.
- def sortable_element(element_id, options = {})
- javascript_tag(sortable_element_js(element_id, options).chop!)
- end
-
- def sortable_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
- options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- [:tag, :overlap, :constraint, :handle].each do |option|
- options[option] = "'#{options[option]}'" if options[option]
- end
-
- options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
- options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
-
- %(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ draggable.
- #
- # Example:
- # <%= draggable_element("my_image", :revert => true)
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def draggable_element(element_id, options = {})
- javascript_tag(draggable_element_js(element_id, options).chop!)
- end
-
- def draggable_element_js(element_id, options = {}) #:nodoc:
- %(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ receive
- # dropped draggable elements (created by +draggable_element+).
- # and make an AJAX call. By default, the action called gets the DOM ID
- # of the element as parameter.
- #
- # Example:
- # <%= drop_receiving_element("my_cart", :url =>
- # { :controller => "cart", :action => "add" }) %>
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- #
- # Some of these +options+ include:
- # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
- # allowable CSS classes that the +draggable_element+ must have in order
- # to be accepted by this +drop_receiving_element+.
- #
- # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
- #
- # :confirm => "Are you sure you want to do this?"
- #
- # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
- # this additional CSS class when an accepted +draggable_element+ is
- # hovered over it.
- #
- # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
- # this element. Override this callback with a JavaScript expression to
- # change the default drop behaviour. Example:
- #
- # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
- #
- # This callback gets three parameters: The Draggable element, the Droppable
- # element and the Event object. You can extract additional information about
- # the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
- #
- # * <tt>:with</tt> - A JavaScript expression specifying the parameters for
- # the XMLHttpRequest. Any expressions should return a valid URL query string.
- def drop_receiving_element(element_id, options = {})
- javascript_tag(drop_receiving_element_js(element_id, options).chop!)
- end
-
- def drop_receiving_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "'id=' + encodeURIComponent(element.id)"
- options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
- options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
-
- # Confirmation happens during the onDrop callback, so it can be removed from the options
- options.delete(:confirm) if options[:confirm]
-
- %(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- protected
- def array_or_string_for_javascript(option)
- if option.kind_of?(Array)
- "['#{option.join('\',\'')}']"
- elsif !option.nil?
- "'#{option}'"
- end
- end
- end
-
- module PrototypeHelper
- class JavaScriptGenerator
- module GeneratorMethods
- # Starts a script.aculo.us visual effect. See
- # ActionView::Helpers::ScriptaculousHelper for more information.
- def visual_effect(name, id = nil, options = {})
- record @context.send(:visual_effect, name, id, options)
- end
-
- # Creates a script.aculo.us sortable element. Useful
- # to recreate sortable elements after items get added
- # or deleted.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def sortable(id, options = {})
- record @context.send(:sortable_element_js, id, options)
- end
-
- # Creates a script.aculo.us draggable element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def draggable(id, options = {})
- record @context.send(:draggable_element_js, id, options)
- end
-
- # Creates a script.aculo.us drop receiving element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def drop_receiving(id, options = {})
- record @context.send(:drop_receiving_element_js, id, options)
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb
new file mode 100644
index 0000000000..408a2030ab
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb
@@ -0,0 +1,85 @@
+require 'uri'
+
+module ActionView
+ module Helpers
+ module SprocketsHelper
+ def sprockets_javascript_path(source)
+ compute_sprockets_path source, 'assets', 'js'
+ end
+
+ def sprockets_javascript_include_tag(source, options = {})
+ options = {
+ 'type' => "text/javascript",
+ 'src' => sprockets_javascript_path(source)
+ }.merge(options.stringify_keys)
+
+ content_tag 'script', "", options
+ end
+
+ def sprockets_stylesheet_path(source)
+ compute_sprockets_path source, 'assets', 'css'
+ end
+
+ def sprockets_stylesheet_link_tag(source, options = {})
+ options = {
+ 'rel' => "stylesheet",
+ 'type' => "text/css",
+ 'media' => "screen",
+ 'href' => sprockets_stylesheet_path(source)
+ }.merge(options.stringify_keys)
+
+ tag 'link', options
+ end
+
+ private
+ def compute_sprockets_path(source, dir, default_ext)
+ source = source.to_s
+
+ return source if URI.parse(source).host
+
+ # Add /javscripts to relative paths
+ if source[0] != ?/
+ source = "/#{dir}/#{source}"
+ end
+
+ # Add default extension if there isn't one
+ if default_ext && File.extname(source).empty?
+ source = "#{source}.#{default_ext}"
+ end
+
+ # Fingerprint url
+ if source =~ /^\/#{dir}\/(.+)/
+ source = assets.path($1, config.perform_caching, dir)
+ end
+
+ host = compute_asset_host(source)
+
+ if controller.respond_to?(:request) && host && URI.parse(host).host
+ source = "#{controller.request.protocol}#{host}#{source}"
+ end
+
+ source
+ end
+
+ def compute_asset_host(source)
+ if host = config.asset_host
+ if host.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = controller.respond_to?(:request) && controller.request
+ host.call(source, request)
+ else
+ host.call(source)
+ end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
+ end
+ end
+ end
+
+ def assets
+ Rails.application.assets
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 2d3c5fe7e7..bdda1df437 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -303,7 +303,7 @@ module ActionView
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
def auto_link(text, *args, &block)#link = :all, html = {}, &block)
- return ''.html_safe if text.blank?
+ return '' if text.blank?
options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
unless args.empty?
@@ -507,7 +507,7 @@ module ActionView
end
content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
end
- end.html_safe
+ end
end
# Turns all email addresses into clickable links. If a block is given,
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 2cd2dca711..de75488e72 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -81,9 +81,12 @@ module ActionView
# # => /workshops
#
# <%= url_for(@workshop) %>
- # # calls @workshop.to_s
+ # # calls @workshop.to_param which by default returns the id
# # => /workshops/5
#
+ # # to_param can be re-defined in a model to provide different URL names:
+ # # => /workshops/1-workshop-name
+ #
# <%= url_for("http://www.example.com") %>
# # => http://www.example.com
#
@@ -183,7 +186,7 @@ module ActionView
# link_to "Profiles", :controller => "profiles"
# # => <a href="/profiles">Profiles</a>
#
- # You can use a block as well if your link target is hard to fit into the name parameter. ERb example:
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
#
# <%= link_to(@profile) do %>
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e3de3e1eac..8b840a6463 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -15,6 +15,7 @@ module ActionView #:nodoc:
end
def find_all(path, prefixes = [], *args)
+ prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
each do |resolver|
templates = resolver.find_all(path, prefix, *args)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 501ec07b09..f20ba7e6d3 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -6,7 +6,7 @@ module ActionView
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
- config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] }
+ config.action_view.javascript_expansions = { :defaults => %w(jquery rails) }
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 94c0a8a8fb..10cd37d56f 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,3 @@
-require 'action_view/renderer/abstract_renderer'
-
module ActionView
class PartialRenderer < AbstractRenderer #:nodoc:
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
@@ -79,7 +77,7 @@ module ActionView
locals[as] = object
content = @template.render(view, locals) do |*name|
- view._layout_for(*name, &block)
+ view._block_layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
new file mode 100644
index 0000000000..52f0e9f5bd
--- /dev/null
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -0,0 +1,129 @@
+# 1.9 ships with Fibers but we need to require the extra
+# methods explicitly. We only load those extra methods if
+# Fiber is available in the first place.
+require 'fiber' if defined?(Fiber)
+
+module ActionView
+ # Consider the following layout:
+ #
+ # <%= yield :header %>
+ # 2
+ # <%= yield %>
+ # 5
+ # <%= yield :footer %>
+ #
+ # And template:
+ #
+ # <%= provide :header, "1" %>
+ # 3
+ # 4
+ # <%= provide :footer, "6" %>
+ #
+ # It will stream:
+ #
+ # "1\n", "2\n", "3\n4\n", "5\n", "6\n"
+ #
+ # Notice that once you <%= yield %>, it will render the whole template
+ # before streaming again. In the future, we can also support streaming
+ # from the template and not only the layout.
+ #
+ # Also, notice we use +provide+ instead of +content_for+, as +provide+
+ # gives the control back to the layout as soon as it is called.
+ # With +content_for+, it would render all the template to find all
+ # +content_for+ calls. For instance, consider this layout:
+ #
+ # <%= yield :header %>
+ #
+ # With this template:
+ #
+ # <%= content_for :header, "1" %>
+ # <%= provide :header, "2" %>
+ # <%= provide :header, "3" %>
+ #
+ # It will return "12\n" because +content_for+ continues rendering the
+ # template but it is returns back to the layout as soon as it sees the
+ # first +provide+.
+ #
+ # == TODO
+ #
+ # * Add streaming support in the controllers with no-cache settings
+ # * What should happen when an error happens?
+ # * Support streaming from child templates, partials and so on.
+ # * Support on sprockets async JS load?
+ #
+ class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
+ # A valid Rack::Body (i.e. it responds to each).
+ # It is initialized with a block that, when called, starts
+ # rendering the template.
+ class Body #:nodoc:
+ def initialize(&start)
+ @start = start
+ end
+
+ def each(&block)
+ @start.call(block)
+ self
+ end
+ end
+
+ # For streaming, instead of rendering a given a template, we return a Body
+ # object that responds to each. This object is initialized with a block
+ # that knows how to render the template.
+ def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ return [super] unless layout_name && template.supports_streaming?
+
+ locals ||= {}
+ layout = layout_name && find_layout(layout_name, locals.keys)
+
+ Body.new do |buffer|
+ delayed_render(buffer, template, layout, @view, locals)
+ end
+ end
+
+ private
+
+ def delayed_render(buffer, template, layout, view, locals)
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
+ # underlying template handler. Now, everytime something is concatenated
+ # to the buffer, it is not appended to an array, but streamed straight
+ # to the client.
+ output = ActionView::StreamingBuffer.new(buffer)
+ yielder = lambda { |*name| view._layout_for(*name) }
+
+ instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
+ fiber = Fiber.new do
+ if layout
+ layout.render(view, locals, output, &yielder)
+ else
+ # If you don't have a layout, just render the thing
+ # and concatenate the final result. This is the same
+ # as a layout with just <%= yield %>
+ output.safe_concat view._layout_for
+ end
+ end
+
+ # Set the view flow to support streaming. It will be aware
+ # when to stop rendering the layout because it needs to search
+ # something in the template and vice-versa.
+ view._view_flow = StreamingFlow.new(view, fiber)
+
+ # Yo! Start the fiber!
+ fiber.resume
+
+ # If the fiber is still alive, it means we need something
+ # from the template, so start rendering it. If not, it means
+ # the layout exited without requiring anything from the template.
+ if fiber.alive?
+ content = template.render(view, locals, &yielder)
+
+ # Once rendering the template is done, sets its content in the :layout key.
+ view._view_flow.set(:layout, content)
+
+ # In case the layout continues yielding, we need to resume
+ # the fiber until all yields are handled.
+ fiber.resume while fiber.alive?
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index 9ae1636131..6b5ead463f 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -1,41 +1,16 @@
-require 'set'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/array/wrap'
-require 'action_view/renderer/abstract_renderer'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
- attr_reader :rendered
-
- def initialize(view)
- super
- @rendered = Set.new
- end
-
def render(options)
wrap_formats(options[:template] || options[:file]) do
template = determine_template(options)
+ freeze_formats(template.formats, true)
render_template(template, options[:layout], options[:locals])
end
end
- def render_once(options)
- paths, locals = options[:once], options[:locals] || {}
- layout, keys = options[:layout], locals.keys
- prefixes = options.fetch(:prefixes, @view.controller_prefixes)
-
- raise "render :once expects a String or an Array to be given" unless paths
-
- render_with_layout(layout, locals) do
- contents = []
- Array.wrap(paths).each do |path|
- template = find_template(path, prefixes, false, keys)
- contents << render_template(template, nil, locals) if @rendered.add?(template)
- end
- contents.join("\n")
- end
- end
-
# Determine the template to be rendered using the given options.
def determine_template(options) #:nodoc:
keys = options[:locals].try(:keys) || []
@@ -56,7 +31,6 @@ module ActionView
# Renders the given template. An string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- freeze_formats(template.formats, true)
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
@@ -72,7 +46,7 @@ module ActionView
if layout
view = @view
- view.store_content_for(:layout, content)
+ view._view_flow.set(:layout, content)
layout.render(view, locals){ |*name| view._layout_for(*name) }
else
content
diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb
index baa5d2c3fd..2bce2fb045 100644
--- a/actionpack/lib/action_view/rendering.rb
+++ b/actionpack/lib/action_view/rendering.rb
@@ -6,11 +6,9 @@ module ActionView
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
# * <tt>:partial</tt> - See ActionView::Partials.
- # * <tt>:update</tt> - Calls update_page with the block given.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
- # * <tt>:once</tt> - Accepts a string or an array of strings and Rails will ensure they each of them are rendered just once.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
@@ -21,18 +19,27 @@ module ActionView
_render_partial(options.merge(:partial => options[:layout]), &block)
elsif options.key?(:partial)
_render_partial(options)
- elsif options.key?(:once)
- _render_once(options)
else
_render_template(options)
end
- when :update
- update_page(&block)
else
_render_partial(:partial => options, :locals => locals)
end
end
+ # Render but returns a valid Rack body. If fibers are defined, we return
+ # a streaming body that renders the template piece by piece.
+ #
+ # Note that partials are not supported to be rendered with streaming,
+ # so in such cases, we just wrap them in an array.
+ def render_body(options)
+ if options.key?(:partial)
+ [_render_partial(options)]
+ else
+ StreamingTemplateRenderer.new(self).render(options)
+ end
+ end
+
# Returns the contents that are yielded to a layout, given a name or a block.
#
# You can think of a layout as a method that is called with a block. If the user calls
@@ -79,22 +86,23 @@ module ActionView
# Hello David
# </html>
#
- def _layout_for(*args, &block)
+ def _layout_for(*args)
+ name = args.first
+ name = :layout unless name.is_a?(Symbol)
+ @_view_flow.get(name).html_safe
+ end
+
+ # Handle layout for calls from partials that supports blocks.
+ def _block_layout_for(*args, &block)
name = args.first
- if name.is_a?(Symbol)
- @_content_for[name].html_safe
- elsif block
+ if !name.is_a?(Symbol) && block
capture(*args, &block)
else
- @_content_for[:layout].html_safe
+ _layout_for(*args)
end
end
- def _render_once(options) #:nodoc:
- _template_renderer.render_once(options)
- end
-
def _render_template(options) #:nodoc:
_template_renderer.render(options)
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 96d506fac5..6dfc4f68ae 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -126,17 +126,23 @@ module ActionView
@formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
+ # Returns if the underlying handler supports streaming. If so,
+ # a streaming buffer *may* be passed when it start rendering.
+ def supports_streaming?
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
+ end
+
# Render a template. If the template was not compiled yet, it is done
# exactly before rendering.
#
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, &block)
+ def render(view, locals, buffer=nil, &block)
old_template, view._template = view._template, self
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
compile!(view)
- view.send(method_name, locals, &block)
+ view.send(method_name, locals, buffer, &block)
end
rescue Exception => e
handle_render_error(view, e)
@@ -167,28 +173,6 @@ module ActionView
end
end
- # Expires this template by setting his updated_at date to Jan 1st, 1970.
- def expire!
- @updated_at = Time.utc(1970)
- end
-
- # Receives a view context and renders a template exactly like self by using
- # the @virtual_path. It raises an error if no @virtual_path was given.
- def rerender(view)
- raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path
- name = @virtual_path.dup
- if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2')
- view.render :partial => name
- else
- view.render :template => @virtual_path
- end
- end
-
- # Used to store template data by template handlers.
- def data
- @data ||= {}
- end
-
def inspect
@inspect ||=
if defined?(Rails.root)
@@ -274,13 +258,12 @@ module ActionView
end
end
- arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity
- code = arity.abs == 1 ? @handler.call(self) : @handler.call(self, view)
+ code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
# encoding of the code
source = <<-end_src
- def #{method_name}(local_assigns)
+ def #{method_name}(local_assigns, output_buffer)
_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@output_buffer = _old_output_buffer
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 4438199497..959afa734e 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -3,12 +3,10 @@ module ActionView #:nodoc:
class Template
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
- autoload :RJS, 'action_view/template/handlers/rjs'
autoload :Builder, 'action_view/template/handlers/builder'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
- base.register_template_handler :rjs, RJS.new
base.register_template_handler :builder, Builder.new
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index a36837afc8..7e9e4e518a 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,28 +1,14 @@
require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/string/output_safety'
require 'action_view/template'
require 'action_view/template/handler'
require 'erubis'
module ActionView
- class OutputBuffer < ActiveSupport::SafeBuffer
- def initialize(*)
- super
- encode! if encoding_aware?
- end
-
- def <<(value)
- super(value.to_s)
- end
- alias :append= :<<
- alias :safe_append= :safe_concat
- end
-
class Template
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
- src << "@output_buffer = ActionView::OutputBuffer.new;"
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
@@ -55,7 +41,7 @@ module ActionView
class ERB
# Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERb documentation for suitable values.
+ # See ERB documentation for suitable values.
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
@@ -73,6 +59,10 @@ module ActionView
new.call(template)
end
+ def supports_streaming?
+ true
+ end
+
def handles_encoding?
true
end
diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb
deleted file mode 100644
index 9d71059134..0000000000
--- a/actionpack/lib/action_view/template/handlers/rjs.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Template::Handlers
- class RJS
- # Default format used by RJS.
- class_attribute :default_format
- self.default_format = Mime::JS
-
- def call(template)
- "update_page do |page|;#{template.source}\nend"
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 41c6310ae2..870897958a 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -186,7 +186,7 @@ module ActionView
# ==== Examples
#
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
- # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml,rjs},}`
+ # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 3e2ddffa16..5c74bf843a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -121,7 +121,7 @@ module ActionView
# Support the selector assertions
#
# Need to experiment if this priority is the best one: rendered => output_buffer
- def response_from_page_or_rjs
+ def response_from_page
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
new file mode 100644
index 0000000000..fe3c8c9783
--- /dev/null
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -0,0 +1,56 @@
+module Sprockets
+ class Railtie < Rails::Railtie
+ def self.using_coffee?
+ require 'coffee-script'
+ defined?(CoffeeScript)
+ rescue LoadError
+ false
+ end
+
+ def self.using_scss?
+ require 'sass'
+ defined?(Sass)
+ rescue LoadError
+ false
+ end
+
+ config.app_generators.javascript_engine :coffee if using_coffee?
+ config.app_generators.stylesheet_engine :scss if using_scss?
+
+ # Configure ActionController to use sprockets.
+ initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app|
+ ActiveSupport.on_load(:action_controller) do
+ self.use_sprockets = app.config.assets.enabled
+ end
+ end
+
+ # We need to configure this after initialization to ensure we collect
+ # paths from all engines. This hook is invoked exactly before routes
+ # are compiled.
+ config.after_initialize do |app|
+ assets = app.config.assets
+ next unless assets.enabled
+
+ app.assets = asset_environment(app)
+ app.routes.append do
+ mount app.assets => assets.prefix
+ end
+
+ if config.action_controller.perform_caching
+ app.assets = app.assets.index
+ end
+ end
+
+ protected
+
+ def asset_environment(app)
+ require "sprockets"
+ assets = app.config.assets
+ env = Sprockets::Environment.new(app.root.to_s)
+ env.static_root = File.join(app.root.join("public"), assets.prefix)
+ env.paths.concat assets.paths
+ env.logger = Rails.logger
+ env
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index f63321c78b..878484eb57 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -34,13 +34,6 @@ class AssertSelectTest < ActionController::TestCase
@content = nil
end
- def rjs()
- render :update do |page|
- @update.call page
- end
- @update = nil
- end
-
def xml()
render :text=>@content, :layout=>false, :content_type=>Mime::XML
@content = nil
@@ -219,50 +212,6 @@ class AssertSelectTest < ActionController::TestCase
end
end
- # With single result.
- def test_assert_select_from_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
- assert_select "div" do |elements|
- assert elements.size == 2
- assert_select "#1"
- assert_select "#2"
- end
- assert_select "div#?", /\d+/ do |elements|
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- # With multiple results.
- def test_assert_select_from_rjs_with_multiple_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
- assert_select "div" do |elements|
- assert elements.size == 2
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_arguments
- render_rjs do |page|
- page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
- page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
- end
- assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"}
- end
-
- def test_assert_select_rjs_for_redirect_to
- render_rjs do |page|
- page.redirect_to '/'
- end
- assert_select_rjs :redirect, '/'
- end
-
def test_elect_with_xml_namespace_attributes
render_html %Q{<link xlink:href="http://nowhere.com"></link>}
assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" }
@@ -296,364 +245,6 @@ class AssertSelectTest < ActionController::TestCase
end
end
- # With one result.
- def test_css_select_from_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
- assert_equal 2, css_select("div").size
- assert_equal 1, css_select("#1").size
- assert_equal 1, css_select("#2").size
- end
-
- # With multiple results.
- def test_css_select_from_rjs_with_multiple_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
-
- assert_equal 2, css_select("div").size
- assert_equal 1, css_select("#1").size
- assert_equal 1, css_select("#2").size
- end
-
- #
- # Test assert_select_rjs.
- #
-
- # Test that we can pick up all statements in the result.
- def test_assert_select_rjs_picks_up_all_statements
- render_rjs do |page|
- page.replace "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
-
- found = false
- assert_select_rjs do
- assert_select "#1"
- assert_select "#2"
- assert_select "#3"
- found = true
- end
- assert found
- end
-
- # Test that we fail if there is nothing to pick.
- def test_assert_select_rjs_fails_if_nothing_to_pick
- render_rjs { }
- assert_raise(Assertion) { assert_select_rjs }
- end
-
- def test_assert_select_rjs_with_unicode
- # Test that non-ascii characters (which are converted into \uXXXX in RJS) are decoded correctly.
- render_rjs do |page|
- page.replace "test", "<div id=\"1\">\343\203\201\343\202\261\343\203\203\343\203\210</div>"
- end
- assert_select_rjs do
- str = "#1"
- assert_select str, :text => "\343\203\201\343\202\261\343\203\203\343\203\210"
- assert_select str, "\343\203\201\343\202\261\343\203\203\343\203\210"
- if str.respond_to?(:force_encoding)
- assert_select str, /\343\203\201..\343\203\210/u
- assert_raise(Assertion) { assert_select str, /\343\203\201.\343\203\210/u }
- else
- assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U')
- assert_raise(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') }
- end
- end
- end
-
- def test_assert_select_rjs_with_id
- # Test that we can pick up all statements in the result.
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- assert_select_rjs "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs "test3" do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_raise(Assertion) { assert_select_rjs "test4" }
- end
-
- def test_assert_select_rjs_for_replace
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- # Replace.
- assert_select_rjs :replace do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :replace, "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_raise(Assertion) { assert_select_rjs :replace, "test2" }
- # Replace HTML.
- assert_select_rjs :replace_html do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :replace_html, "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
- end
-
- def test_assert_select_rjs_for_chained_replace
- render_rjs do |page|
- page['test1'].replace "<div id=\"1\">foo</div>"
- page['test2'].replace_html "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- # Replace.
- assert_select_rjs :chained_replace do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :chained_replace, "test1" do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_raise(Assertion) { assert_select_rjs :chained_replace, "test2" }
- # Replace HTML.
- assert_select_rjs :chained_replace_html do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :chained_replace_html, "test2" do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" }
- end
-
- # Simple remove
- def test_assert_select_rjs_for_remove
- render_rjs do |page|
- page.remove "test1"
- end
-
- assert_select_rjs :remove, "test1"
- end
-
- def test_assert_select_rjs_for_remove_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.remove "test_with_typo"
- end
-
- assert_select_rjs :remove, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that removes 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_remove_ignores_block
- render_rjs do |page|
- page.remove "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :remove, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple show
- def test_assert_select_rjs_for_show
- render_rjs do |page|
- page.show "test1"
- end
-
- assert_select_rjs :show, "test1"
- end
-
- def test_assert_select_rjs_for_show_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.show "test_with_typo"
- end
-
- assert_select_rjs :show, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that shows 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_show_ignores_block
- render_rjs do |page|
- page.show "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :show, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple hide
- def test_assert_select_rjs_for_hide
- render_rjs do |page|
- page.hide "test1"
- end
-
- assert_select_rjs :hide, "test1"
- end
-
- def test_assert_select_rjs_for_hide_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.hide "test_with_typo"
- end
-
- assert_select_rjs :hide, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that hides 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_hide_ignores_block
- render_rjs do |page|
- page.hide "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :hide, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Simple toggle
- def test_assert_select_rjs_for_toggle
- render_rjs do |page|
- page.toggle "test1"
- end
-
- assert_select_rjs :toggle, "test1"
- end
-
- def test_assert_select_rjs_for_toggle_offers_useful_error_when_assertion_fails
- render_rjs do |page|
- page.toggle "test_with_typo"
- end
-
- assert_select_rjs :toggle, "test1"
-
- rescue Assertion => e
- assert_equal "No RJS statement that toggles 'test1' was rendered.", e.message
- end
-
- def test_assert_select_rjs_for_toggle_ignores_block
- render_rjs do |page|
- page.toggle "test1"
- end
-
- assert_nothing_raised do
- assert_select_rjs :toggle, "test1" do
- assert_select "p"
- end
- end
- end
-
- # Non-positioned insert.
- def test_assert_select_rjs_for_nonpositioned_insert
- render_rjs do |page|
- page.replace "test1", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- page.insert_html :top, "test3", "<div id=\"3\">foo</div>"
- end
- assert_select_rjs :insert_html do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_select_rjs :insert_html, "test3" do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_raise(Assertion) { assert_select_rjs :insert_html, "test1" }
- end
-
- # Positioned insert.
- def test_assert_select_rjs_for_positioned_insert
- render_rjs do |page|
- page.insert_html :top, "test1", "<div id=\"1\">foo</div>"
- page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>"
- page.insert_html :before, "test3", "<div id=\"3\">foo</div>"
- page.insert_html :after, "test4", "<div id=\"4\">foo</div>"
- end
- assert_select_rjs :insert, :top do
- assert_select "div", 1
- assert_select "#1"
- end
- assert_select_rjs :insert, :bottom do
- assert_select "div", 1
- assert_select "#2"
- end
- assert_select_rjs :insert, :before do
- assert_select "div", 1
- assert_select "#3"
- end
- assert_select_rjs :insert, :after do
- assert_select "div", 1
- assert_select "#4"
- end
- assert_select_rjs :insert_html do
- assert_select "div", 4
- end
- end
-
- def test_assert_select_rjs_raise_errors
- assert_raise(ArgumentError) { assert_select_rjs(:destroy) }
- assert_raise(ArgumentError) { assert_select_rjs(:insert, :left) }
- end
-
- # Simple selection from a single result.
- def test_nested_assert_select_rjs_with_single_result
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>"
- end
-
- assert_select_rjs "test" do |elements|
- assert_equal 2, elements.size
- assert_select "#1"
- assert_select "#2"
- end
- end
-
- # Deal with two results.
- def test_nested_assert_select_rjs_with_two_results
- render_rjs do |page|
- page.replace_html "test", "<div id=\"1\">foo</div>"
- page.replace_html "test2", "<div id=\"2\">foo</div>"
- end
-
- assert_select_rjs "test" do |elements|
- assert_equal 1, elements.size
- assert_select "#1"
- end
-
- assert_select_rjs "test2" do |elements|
- assert_equal 1, elements.size
- assert_select "#2"
- end
- end
-
def test_feed_item_encoded
render_xml <<-EOF
<rss version="2.0">
@@ -728,11 +319,6 @@ EOF
get :html
end
- def render_rjs(&block)
- @controller.response_with(&block)
- get :rjs
- end
-
def render_xml(xml)
@controller.response_with = xml
get :xml
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 01f3e8f2b6..fada0c7748 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -713,17 +713,10 @@ class FunctionalCachingController < CachingController
end
end
- def js_fragment_cached_with_partial
- respond_to do |format|
- format.js
- end
- end
-
def formatted_fragment_cached
respond_to do |format|
format.html
format.xml
- format.js
end
end
@@ -770,13 +763,6 @@ CACHED
assert_match("Some cached content", @store.read('views/test.host/functional_caching/inline_fragment_cached'))
end
- def test_fragment_caching_in_rjs_partials
- xhr :get, :js_fragment_cached_with_partial
- assert_response :success
- assert_match(/Old fragment caching in a partial/, @response.body)
- assert_match("Old fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial'))
- end
-
def test_html_formatted_fragment_caching
get :formatted_fragment_cached, :format => "html"
assert_response :success
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 9500c25a32..b12c798302 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -35,9 +35,6 @@ class OldContentTypeController < ActionController::Base
def render_default_for_builder
end
- def render_default_for_rjs
- end
-
def render_change_for_builder
response.content_type = Mime::HTML
render :action => "render_default_for_builder"
@@ -129,12 +126,6 @@ class ContentTypeTest < ActionController::TestCase
assert_equal "utf-8", @response.charset
end
- def test_default_for_rjs
- xhr :post, :render_default_for_rjs
- assert_equal Mime::JS, @response.content_type
- assert_equal "utf-8", @response.charset
- end
-
def test_change_for_builder
get :render_change_for_builder
assert_equal Mime::HTML, @response.content_type
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
new file mode 100644
index 0000000000..9b69a2648e
--- /dev/null
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -0,0 +1,90 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ class FlashHashTest < ActiveSupport::TestCase
+ def setup
+ @hash = Flash::FlashHash.new
+ end
+
+ def test_set_get
+ @hash[:foo] = 'zomg'
+ assert_equal 'zomg', @hash[:foo]
+ end
+
+ def test_keys
+ assert_equal [], @hash.keys
+
+ @hash['foo'] = 'zomg'
+ assert_equal ['foo'], @hash.keys
+
+ @hash['bar'] = 'zomg'
+ assert_equal ['foo', 'bar'].sort, @hash.keys.sort
+ end
+
+ def test_update
+ @hash['foo'] = 'bar'
+ @hash.update('foo' => 'baz', 'hello' => 'world')
+
+ assert_equal 'baz', @hash['foo']
+ assert_equal 'world', @hash['hello']
+ end
+
+ def test_delete
+ @hash['foo'] = 'bar'
+ @hash.delete 'foo'
+
+ assert !@hash.key?('foo')
+ assert_nil @hash['foo']
+ end
+
+ def test_to_hash
+ @hash['foo'] = 'bar'
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+
+ @hash.to_hash['zomg'] = 'aaron'
+ assert !@hash.key?('zomg')
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+ end
+
+ def test_empty?
+ assert @hash.empty?
+ @hash['zomg'] = 'bears'
+ assert !@hash.empty?
+ @hash.clear
+ assert @hash.empty?
+ end
+
+ def test_each
+ @hash['hello'] = 'world'
+ @hash['foo'] = 'bar'
+
+ things = []
+ @hash.each do |k,v|
+ things << [k,v]
+ end
+
+ assert_equal([%w{ hello world }, %w{ foo bar }].sort, things.sort)
+ end
+
+ def test_replace
+ @hash['hello'] = 'world'
+ @hash.replace('omg' => 'aaron')
+ assert_equal({'omg' => 'aaron'}, @hash.to_hash)
+ end
+
+ def test_discard_no_args
+ @hash['hello'] = 'world'
+ @hash.discard
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+
+ def test_discard_one_arg
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash.sweep
+ assert_equal({'omg' => 'world'}, @hash.to_hash)
+ end
+ end
+end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 3569a2f213..9c89f1334d 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -174,13 +174,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existant key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard()) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil)) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existant key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep()) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil)) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
@@ -214,11 +214,20 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
class TestController < ActionController::Base
+ def dont_set_flash
+ head :ok
+ end
+
def set_flash
flash["that"] = "hello"
head :ok
end
+ def set_flash_now
+ flash.now["that"] = "hello"
+ head :ok
+ end
+
def use_flash
render :inline => "flash: #{flash["that"]}"
end
@@ -245,6 +254,47 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ def test_setting_flash_raises_after_stream_back_to_client
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_raises_after_stream_back_to_client_even_with_an_empty_flash
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/dont_set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_now_raises_after_stream_back_to_client
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash_now', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash.now['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_flash_now_raises_after_stream_back_to_client_even_with_an_empty_flash
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/dont_set_flash', nil, env
+ assert_raise(ActionDispatch::ClosedError) {
+ @request.flash.now['alert'] = 'alert'
+ }
+ end
+ end
+
+
private
# Overwrite get to send SessionSecret in env hash
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index f0d62b0b13..01dc2f2091 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -521,4 +521,12 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
get '/foo'
assert_raise(NameError) { missing_path }
end
+
+ test "process reuse the env we pass as argument" do
+ env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
+ get '/foo', nil, env
+ assert_equal :get, env[:method]
+ assert_equal 'server', env[:SERVER_NAME]
+ assert_equal 'custom', env['action_dispatch.custom']
+ end
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 41f80d0784..4a5e597500 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'controller/fake_models'
require 'active_support/core_ext/hash/conversions'
+require 'active_support/core_ext/object/inclusion'
class StarStarMimeController < ActionController::Base
layout nil
@@ -72,13 +73,12 @@ class RespondToController < ActionController::Base
def using_defaults
respond_to do |type|
type.html
- type.js
type.xml
end
end
def using_defaults_with_type_list
- respond_to(:html, :js, :xml)
+ respond_to(:html, :xml)
end
def made_for_content_type
@@ -129,7 +129,6 @@ class RespondToController < ActionController::Base
def all_types_with_layout
respond_to do |type|
type.html
- type.js
end
end
@@ -158,7 +157,7 @@ class RespondToController < ActionController::Base
protected
def set_layout
- if ["all_types_with_layout", "iphone_with_html_response_type"].include?(action_name)
+ if action_name.in?(["all_types_with_layout", "iphone_with_html_response_type"])
"respond_to/layouts/standard"
elsif action_name == "iphone_with_html_response_type_without_layout"
"respond_to/layouts/missing"
@@ -298,11 +297,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
- @request.accept = "text/javascript"
- get :using_defaults
- assert_equal "text/javascript", @response.content_type
- assert_equal '$("body").visualEffect("highlight");', @response.body
-
@request.accept = "application/xml"
get :using_defaults
assert_equal "application/xml", @response.content_type
@@ -315,11 +309,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal 'Hello world!', @response.body
- @request.accept = "text/javascript"
- 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
assert_equal "application/xml", @response.content_type
@@ -427,13 +416,6 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'HTML', @response.body
end
-
- def test_rjs_type_skips_layout
- @request.accept = "text/javascript"
- get :all_types_with_layout
- assert_equal 'RJS for all_types_with_layout', @response.body
- end
-
def test_html_type_with_layout
@request.accept = "text/html"
get :all_types_with_layout
@@ -443,9 +425,6 @@ class RespondToControllerTest < ActionController::TestCase
def test_xhr
xhr :get, :js_or_html
assert_equal 'JS', @response.body
-
- xhr :get, :using_defaults
- assert_equal '$("body").visualEffect("highlight");', @response.body
end
def test_custom_constant
@@ -642,11 +621,6 @@ class RespondWithControllerTest < ActionController::TestCase
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
-
@request.accept = "application/xml"
get :using_resource
assert_equal "application/xml", @response.content_type
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 8ba30944f5..4b70031c90 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -23,8 +23,7 @@ module ContentType
"content_type/implied/i_am_html_erb.html.erb" => "Hello world!",
"content_type/implied/i_am_xml_erb.xml.erb" => "<xml>Hello world!</xml>",
"content_type/implied/i_am_html_builder.html.builder" => "xml.p 'Hello'",
- "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'",
- "content_type/implied/i_am_js_rjs.js.rjs" => "page.alert 'hello'"
+ "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'"
)]
end
@@ -93,12 +92,6 @@ module ContentType
assert_header "Content-Type", "application/xml; charset=utf-8"
end
-
- test "sets Content-Type as text/javascript when rendering *.js" do
- get "/content_type/implied/i_am_js_rjs", "format" => "js"
-
- assert_header "Content-Type", "text/javascript; charset=utf-8"
- end
end
class ExplicitCharsetTest < Rack::TestCase
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 667a9021be..3bb3016fdb 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -3,8 +3,9 @@ require 'abstract_unit'
module RenderImplicitAction
class SimpleController < ::ApplicationController
self.view_paths = [ActionView::FixtureResolver.new(
- "render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
- "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!"
+ "render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
+ "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
+ "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
)]
def hello_world() end
@@ -25,9 +26,17 @@ module RenderImplicitAction
assert_status 200
end
+ test "render an action called not_implemented" do
+ get "/render_implicit_action/simple/not_implemented"
+
+ assert_body "Not Implemented"
+ assert_status 200
+ end
+
test "action_method? returns true for implicit actions" do
assert SimpleController.new.action_method?(:hello_world)
assert SimpleController.new.action_method?(:"hyphen-ated")
+ assert SimpleController.new.action_method?(:not_implemented)
end
end
end
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
index bb2a953536..d3dcb5cad6 100644
--- a/actionpack/test/controller/new_base/render_layout_test.rb
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -70,8 +70,8 @@ module ControllerLayouts
class MismatchFormatController < ::ApplicationController
self.view_paths = [ActionView::FixtureResolver.new(
"layouts/application.html.erb" => "<html><%= yield %></html>",
- "controller_layouts/mismatch_format/index.js.rjs" => "page[:test].ext",
- "controller_layouts/mismatch_format/implicit.rjs" => "page[:test].ext"
+ "controller_layouts/mismatch_format/index.xml.builder" => "xml.instruct!",
+ "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!"
)]
def explicit
@@ -81,15 +81,17 @@ module ControllerLayouts
class MismatchFormatTest < Rack::TestCase
testing ControllerLayouts::MismatchFormatController
+
+ XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
- test "if JS is selected, an HTML template is not also selected" do
- get :index, "format" => "js"
- assert_response "$(\"test\").ext();"
+ test "if XML is selected, an HTML template is not also selected" do
+ get :index, :format => "xml"
+ assert_response XML_INSTRUCT
end
- test "if JS is implicitly selected, an HTML template is not also selected" do
+ test "if XML is implicitly selected, an HTML template is not also selected" do
get :implicit
- assert_response "$(\"test\").ext();"
+ assert_response XML_INSTRUCT
end
test "if an HTML template is explicitly provides for a JS template, an error is raised" do
diff --git a/actionpack/test/controller/new_base/render_once_test.rb b/actionpack/test/controller/new_base/render_once_test.rb
deleted file mode 100644
index 175abf8a7e..0000000000
--- a/actionpack/test/controller/new_base/render_once_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-
-module RenderTemplate
- class RenderOnceController < ActionController::Base
- layout false
-
- RESOLVER = ActionView::FixtureResolver.new(
- "test/a.html.erb" => "a",
- "test/b.html.erb" => "<>",
- "test/c.html.erb" => "c",
- "test/one.html.erb" => "<%= render :once => 'result' %>",
- "test/two.html.erb" => "<%= render :once => 'result' %>",
- "test/three.html.erb" => "<%= render :once => 'result' %>",
- "test/result.html.erb" => "YES!",
- "other/result.html.erb" => "NO!",
- "layouts/test.html.erb" => "l<%= yield %>l"
- )
-
- self.view_paths = [RESOLVER]
-
- def _prefixes
- %w(test)
- end
-
- def multiple
- render :once => %w(a b c)
- end
-
- def once
- render :once => %w(one two three)
- end
-
- def duplicate
- render :once => %w(a a a)
- end
-
- def with_layout
- render :once => %w(a b c), :layout => "test"
- end
-
- def with_prefix
- render :once => "result", :prefixes => %w(other)
- end
-
- def with_nil_prefix
- render :once => "test/result", :prefixes => []
- end
- end
-
- module Tests
- def test_mutliple_arguments_get_all_rendered
- get :multiple
- assert_response "a\n<>\nc"
- end
-
- def test_referenced_templates_get_rendered_once
- get :once
- assert_response "YES!\n\n"
- end
-
- def test_duplicated_templates_get_rendered_once
- get :duplicate
- assert_response "a"
- end
-
- def test_layout_wraps_all_rendered_templates
- get :with_layout
- assert_response "la\n<>\ncl"
- end
-
- def test_with_prefix_option
- get :with_prefix
- assert_response "NO!"
- end
-
- def test_with_nil_prefix_option
- get :with_nil_prefix
- assert_response "YES!"
- end
- end
-
- class TestRenderOnce < Rack::TestCase
- testing RenderTemplate::RenderOnceController
- include Tests
- end
-end
diff --git a/actionpack/test/controller/new_base/render_rjs_test.rb b/actionpack/test/controller/new_base/render_rjs_test.rb
deleted file mode 100644
index 74bf865b54..0000000000
--- a/actionpack/test/controller/new_base/render_rjs_test.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'abstract_unit'
-
-module RenderRjs
- class BasicController < ActionController::Base
- layout "application", :only => :index_respond_to
-
- self.view_paths = [ActionView::FixtureResolver.new(
- "layouts/application.html.erb" => "",
- "render_rjs/basic/index.js.rjs" => "page[:customer].replace_html render(:partial => 'customer')",
- "render_rjs/basic/index_html.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
- "render_rjs/basic/index_no_js.js.erb" => "<%= render(:partial => 'developer') %>",
- "render_rjs/basic/_customer.js.erb" => "JS Partial",
- "render_rjs/basic/_customer.html.erb" => "HTML Partial",
- "render_rjs/basic/_developer.html.erb" => "HTML Partial",
- "render_rjs/basic/index_locale.js.rjs" => "page[:customer].replace_html :partial => 'customer'",
- "render_rjs/basic/_customer.da.html.erb" => "Danish HTML Partial",
- "render_rjs/basic/_customer.da.js.erb" => "Danish JS Partial"
- )]
-
- def index
- render
- end
-
- def index_respond_to
- respond_to do |format|
- format.js { render :action => "index_no_js" }
- end
- end
-
- def index_locale
- self.locale = :da
- end
- end
-
- class TestBasic < Rack::TestCase
- testing BasicController
-
- def setup
- @old_locale = I18n.locale
- end
-
- def teardown
- I18n.locale = @old_locale
- end
-
- test "rendering a partial in an RJS template should pick the JS template over the HTML one" do
- get :index, "format" => "js"
- assert_response("$(\"customer\").update(\"JS Partial\");")
- end
-
- test "rendering a partial in an RJS template should pick the HTML one if no JS is available" do
- get :index_no_js, "format" => "js"
- assert_response("HTML Partial")
- end
-
- test "rendering a partial in an RJS template should pick the HTML one if no JS is available on respond_to" do
- get :index_respond_to, "format" => "js"
- assert_response("HTML Partial")
- end
-
- test "replacing an element with a partial in an RJS template should pick the HTML template over the JS one" do
- get :index_html, "format" => "js"
- assert_response("$(\"customer\").update(\"HTML Partial\");")
- end
-
- test "replacing an element with a partial in an RJS template with a locale should pick the localed HTML template" do
- get :index_locale, "format" => "js"
- assert_response("$(\"customer\").update(\"Danish HTML Partial\");")
- end
- end
-end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index d6062bfa8c..60468bf5c7 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -81,8 +81,7 @@ module Render
end
class TestOnlyRenderPublicActions < Rack::TestCase
- describe "Only public methods on actual controllers are callable actions"
-
+ # Only public methods on actual controllers are callable actions
test "raises an exception when a method of Object is called" do
assert_raises(AbstractController::ActionNotFound) do
get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index 491c98a0fd..f070109b27 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -14,10 +14,6 @@ class RenderJSTest < ActionController::TestCase
render :js => "alert('hello')"
end
- def greeting
- # let's just rely on the template
- end
-
def show_partial
render :partial => 'partial'
end
@@ -31,11 +27,6 @@ class RenderJSTest < ActionController::TestCase
assert_equal "text/javascript", @response.content_type
end
- def test_render_with_default_from_accept_header
- xhr :get, :greeting
- assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
- end
-
def test_should_render_js_partial
xhr :get, :show_partial, :format => 'js'
assert_equal 'partial js', @response.body
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
index eda777e7a7..b5e74e373d 100644
--- a/actionpack/test/controller/render_other_test.rb
+++ b/actionpack/test/controller/render_other_test.rb
@@ -1,6 +1,4 @@
require 'abstract_unit'
-require 'controller/fake_models'
-require 'pathname'
ActionController.add_renderer :simon do |says, options|
self.content_type = Mime::TEXT
@@ -9,248 +7,13 @@ end
class RenderOtherTest < ActionController::TestCase
class TestController < ActionController::Base
- protect_from_forgery
-
- def self.controller_path
- 'test'
- end
-
- layout :determine_layout
-
- module RenderTestHelper
- def rjs_helper_method_from_module
- page.visual_effect :highlight
- end
- end
-
- helper RenderTestHelper
- helper do
- def rjs_helper_method(value)
- page.visual_effect :highlight, value
- end
- end
-
- def enum_rjs_test
- render :update do |page|
- page.select('.product').each do |value|
- page.rjs_helper_method_from_module
- page.rjs_helper_method(value)
- page.sortable(value, :url => { :action => "order" })
- page.draggable(value)
- end
- end
- end
-
- def render_explicit_html_template
- end
-
- def render_custom_code_rjs
- render :update, :status => 404 do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
-
- def render_implicit_html_template
- end
-
- def render_js_with_explicit_template
- @project_id = 4
- render :template => 'test/delete_with_js'
- end
-
- def render_js_with_explicit_action_template
- @project_id = 4
- render :action => 'delete_with_js'
- end
-
- def delete_with_js
- @project_id = 4
- end
-
- def update_page
- render :update do |page|
- page.replace_html 'balance', '$37,000,000.00'
- page.visual_effect :highlight, 'balance'
- end
- end
-
- def update_page_with_instance_variables
- @money = '$37,000,000.00'
- @div_id = 'balance'
- render :update do |page|
- page.replace_html @div_id, @money
- page.visual_effect :highlight, @div_id
- end
- end
-
- def update_page_with_view_method
- render :update do |page|
- page.replace_html 'person', pluralize(2, 'person')
- end
- end
-
- def partial_as_rjs
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
-
- def respond_to_partial_as_rjs
- respond_to do |format|
- format.js do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
- end
-
- def render_alternate_default
- # For this test, the method "default_render" is overridden:
- @alternate_default_render = lambda do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
-
def render_simon_says
render :simon => "foo"
end
-
- private
- def default_render
- @alternate_default_render ||= nil
- if @alternate_default_render
- @alternate_default_render.call
- else
- super
- end
- end
-
- def determine_layout
- case action_name
- when "hello_world", "layout_test", "rendering_without_layout",
- "rendering_nothing_on_layout", "render_text_hello_world",
- "render_text_hello_world_with_layout",
- "hello_world_with_layout_false",
- "partial_only", "partial_only_with_layout",
- "accessing_params_in_template",
- "accessing_params_in_template_with_layout",
- "render_with_explicit_template",
- "render_with_explicit_string_template",
- "update_page", "update_page_with_instance_variables"
-
- "layouts/standard"
- when "action_talk_to_layout", "layout_overriding_layout"
- "layouts/talk_from_action"
- when "render_implicit_html_template_from_xhr_request"
- (request.xhr? ? 'layouts/xhr' : 'layouts/standard')
- end
- end
end
tests TestController
- def setup
- # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
- # a more accurate simulation of what happens in "real life".
- super
- @controller.logger = Logger.new(nil)
-
- @request.host = "www.nextangle.com"
- end
-
- def test_enum_rjs_test
- ActiveSupport::SecureRandom.stubs(:base64).returns("asdf")
- get :enum_rjs_test
- body = %{
- $$(".product").each(function(value, index) {
- new Effect.Highlight(element,{});
- new Effect.Highlight(value,{});
- Sortable.create(value, {onUpdate:function(){new Ajax.Request('/render_other_test/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value) + '&authenticity_token=' + encodeURIComponent('asdf')})}});
- new Draggable(value, {});
- });
- }.gsub(/^ /, '').strip
- assert_equal body, @response.body
- end
-
- def test_explicitly_rendering_an_html_template_with_implicit_html_template_renders_should_be_possible_from_an_rjs_template
- [:js, "js"].each do |format|
- assert_nothing_raised do
- get :render_explicit_html_template, :format => format
- assert_equal %(document.write("Hello world\\n");), @response.body
- end
- end
- end
-
- def test_render_custom_code_rjs
- get :render_custom_code_rjs
- assert_response 404
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_render_in_an_rjs_template_should_pick_html_templates_when_available
- [:js, "js"].each do |format|
- assert_nothing_raised do
- get :render_implicit_html_template, :format => format
- assert_equal %(document.write("Hello world\\n");), @response.body
- end
- end
- end
-
- def test_render_rjs_template_explicitly
- get :render_js_with_explicit_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_rendering_rjs_action_explicitly
- get :render_js_with_explicit_action_template
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_render_rjs_with_default
- get :delete_with_js
- assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body
- end
-
- def test_update_page
- get :update_page
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers['Content-Type']
- assert_equal 2, @response.body.split($/).length
- end
-
- def test_update_page_with_instance_variables
- get :update_page_with_instance_variables
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
- assert_match(/balance/, @response.body)
- assert_match(/\$37/, @response.body)
- end
-
- def test_update_page_with_view_method
- get :update_page_with_view_method
- assert_template nil
- assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"]
- assert_match(/2 people/, @response.body)
- end
-
- def test_should_render_html_formatted_partial_with_rjs
- xhr :get, :partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_should_render_html_formatted_partial_with_rjs_and_js_format
- xhr :get, :respond_to_partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
- def test_should_render_with_alternate_default_render
- xhr :get, :render_alternate_default
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
-
def test_using_custom_render_option
get :render_simon_says
assert_equal "Simon says: foo", @response.body
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index be492152f2..e62f3155c5 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -517,15 +517,6 @@ class TestController < ActionController::Base
render :partial => 'partial'
end
- def render_alternate_default
- # For this test, the method "default_render" is overridden:
- @alternate_default_render = lambda do
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- end
- end
-
def render_to_string_with_partial
@partial_only = render_to_string :partial => "partial_only"
@partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index d520b5e512..31f4bf3a76 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -172,13 +172,11 @@ end
class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
- test 'should emit a csrf-token meta tag' do
+ test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
- assert_equal <<-METAS.strip_heredoc.chomp, @response.body
- <meta name="csrf-param" content="authenticity_token"/>
- <meta name="csrf-token" content="cf50faa3fe97702ca1ae&lt;=?"/>
- METAS
+ assert_select 'meta[name=?][content=?]', 'csrf-param', 'authenticity_token'
+ assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index acb4617a60..6ea492cf8b 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/with_options'
+require 'active_support/core_ext/object/inclusion'
class ResourcesController < ActionController::Base
def index() render :nothing => true end
@@ -1292,7 +1293,7 @@ class ResourcesTest < ActionController::TestCase
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|
- assert resource.send("#{action_method}_methods")[method].include?(action),
+ assert action.in?(resource.send("#{action_method}_methods")[method])
"#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}"
end
end
@@ -1329,9 +1330,9 @@ class ResourcesTest < ActionController::TestCase
options = options.merge(:action => action.to_s)
path_options = { :path => path, :method => method }
- if Array(allowed).include?(action)
+ if action.in?(Array(allowed))
assert_recognizes options, path_options
- elsif Array(not_allowed).include?(action)
+ elsif action.in?(Array(not_allowed))
assert_not_recognizes options, path_options
end
end
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index edfcb5cc4d..9280a1c2d3 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -131,6 +131,35 @@ class ViewLoadPathsTest < ActionController::TestCase
assert_equal "Hello overridden world!", @response.body
end
+ def test_override_view_paths_with_custom_resolver
+ resolver_class = Class.new(ActionView::PathResolver) do
+ def initialize(path_set)
+ @path_set = path_set
+ end
+
+ def find_all(*args)
+ @path_set.find_all(*args).collect do |template|
+ ::ActionView::Template.new(
+ "Customized body",
+ template.identifier,
+ template.handler,
+ {
+ :virtual_path => template.virtual_path,
+ :format => template.formats
+ }
+ )
+ end
+ end
+ end
+
+ resolver = resolver_class.new(TestController.view_paths)
+ TestController.view_paths = ActionView::PathSet.new.push(resolver)
+
+ get :hello_world
+ assert_response :success
+ assert_equal "Customized body", @response.body
+ end
+
def test_inheritance
original_load_paths = ActionController::Base.view_paths
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 39159fd629..ebc16694db 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -495,3 +495,99 @@ class CookiesTest < ActionController::TestCase
end
end
end
+
+class CookiesIntegrationTest < ActionDispatch::IntegrationTest
+ SessionKey = '_myapp_session'
+ SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+
+ class TestController < ActionController::Base
+ def dont_set_cookies
+ head :ok
+ end
+
+ def set_cookies
+ cookies["that"] = "hello"
+ head :ok
+ end
+ end
+
+ def test_setting_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_permanent_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.permanent['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_permanent_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.permanent['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_signed_cookies_raises_after_stream_back_to_client
+ with_test_route_set do
+ get '/set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.signed['alert'] = 'alert'
+ cookies['alert'] = 'alert'
+ }
+ end
+ end
+
+ def test_setting_signed_cookies_raises_after_stream_back_to_client_even_without_cookies
+ with_test_route_set do
+ get '/dont_set_cookies'
+ assert_raise(ActionDispatch::ClosedError) {
+ request.cookie_jar.signed['alert'] = 'alert'
+ }
+ end
+ end
+
+ private
+
+ # Overwrite get to send SessionSecret in env hash
+ def get(path, parameters = nil, env = {})
+ env["action_dispatch.secret_token"] ||= SessionSecret
+ super
+ end
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => CookiesIntegrationTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Cookies
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5e5758a60e..ba7506721f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'abstract_unit'
require 'controller/fake_controllers'
+require 'active_support/core_ext/object/inclusion'
class TestRoutingMapper < ActionDispatch::IntegrationTest
SprocketsApp = lambda { |env|
@@ -495,7 +496,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :todos, :id => /\d+/
end
- scope '/countries/:country', :constraints => lambda { |params, req| %[all France].include?(params[:country]) } do
+ scope '/countries/:country', :constraints => lambda { |params, req| params[:country].in?(["all", "France"]) } do
match '/', :to => 'countries#index'
match '/cities', :to => 'countries#cities'
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 655745a848..2ebbed4414 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -47,35 +47,4 @@ class StaticTest < ActiveSupport::TestCase
end
include StaticTests
-end
-
-class MultipleDirectorisStaticTest < ActiveSupport::TestCase
- DummyApp = lambda { |env|
- [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
- }
- App = ActionDispatch::Static.new(DummyApp,
- { "/" => "#{FIXTURE_LOAD_PATH}/public",
- "/blog" => "#{FIXTURE_LOAD_PATH}/blog_public",
- "/foo" => "#{FIXTURE_LOAD_PATH}/non_existing_dir"
- })
-
- def setup
- @app = App
- end
-
- include StaticTests
-
- test "serves files from other mounted directories" do
- assert_html "/blog/index.html", get("/blog/index.html")
- assert_html "/blog/index.html", get("/blog/index")
- assert_html "/blog/index.html", get("/blog/")
-
- assert_html "/blog/blog.html", get("/blog/blog/")
- assert_html "/blog/blog.html", get("/blog/blog.html")
- assert_html "/blog/blog.html", get("/blog/blog")
-
- assert_html "/blog/subdir/index.html", get("/blog/subdir/index.html")
- assert_html "/blog/subdir/index.html", get("/blog/subdir/")
- assert_html "/blog/subdir/index.html", get("/blog/subdir")
- end
-end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs
deleted file mode 100644
index 057f15e62f..0000000000
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs
+++ /dev/null
@@ -1,6 +0,0 @@
-page.assign 'title', 'Hey'
-cache do
- page['element_1'].visual_effect :highlight
- page['element_2'].visual_effect :highlight
-end
-page.assign 'footer', 'Bye'
diff --git a/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs b/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs
deleted file mode 100644
index 248842c9da..0000000000
--- a/actionpack/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.replace_html 'notices', :partial => 'partial' \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/streaming.erb b/actionpack/test/fixtures/layouts/streaming.erb
new file mode 100644
index 0000000000..d3f896a6ca
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/streaming.erb
@@ -0,0 +1,4 @@
+<%= yield :header -%>
+<%= yield -%>
+<%= yield :footer -%>
+<%= yield(:unknown).presence || "." -%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs b/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs
deleted file mode 100644
index 8d614d04ad..0000000000
--- a/actionpack/test/fixtures/old_content_type/render_default_for_rjs.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.alert 'hello world!' \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs b/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
deleted file mode 100644
index b7aec7c505..0000000000
--- a/actionpack/test/fixtures/respond_to/all_types_with_layout.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page << "RJS for all_types_with_layout" \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults.js.rjs b/actionpack/test/fixtures/respond_to/using_defaults.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_to/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_to/using_defaults_with_type_list.js.rjs b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/respond_to/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_resource.js.rjs b/actionpack/test/fixtures/respond_with/using_resource.js.rjs
deleted file mode 100644
index 737c175a4e..0000000000
--- a/actionpack/test/fixtures/respond_with/using_resource.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/application.js b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
diff --git a/actionpack/test/fixtures/test/delete_with_js.rjs b/actionpack/test/fixtures/test/delete_with_js.rjs
deleted file mode 100644
index 4b75a955ad..0000000000
--- a/actionpack/test/fixtures/test/delete_with_js.rjs
+++ /dev/null
@@ -1,2 +0,0 @@
-page.remove 'person'
-page.visual_effect :highlight, "project-#{@project_id}"
diff --git a/actionpack/test/fixtures/test/enum_rjs_test.rjs b/actionpack/test/fixtures/test/enum_rjs_test.rjs
deleted file mode 100644
index e3004076a8..0000000000
--- a/actionpack/test/fixtures/test/enum_rjs_test.rjs
+++ /dev/null
@@ -1,6 +0,0 @@
-page.select('.product').each do |value|
- page.visual_effect :highlight
- page.visual_effect :highlight, value
- page.sortable(value, :url => { :action => "order" })
- page.draggable(value)
-end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/greeting.js.rjs b/actionpack/test/fixtures/test/greeting.js.rjs
deleted file mode 100644
index 469fcd8e15..0000000000
--- a/actionpack/test/fixtures/test/greeting.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page[:body].visual_effect :highlight \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/nested_streaming.erb b/actionpack/test/fixtures/test/nested_streaming.erb
new file mode 100644
index 0000000000..55525e0c92
--- /dev/null
+++ b/actionpack/test/fixtures/test/nested_streaming.erb
@@ -0,0 +1,3 @@
+<%- content_for :header do -%>?<%- end -%>
+<%= render :template => "test/streaming" %>
+? \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs b/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs
deleted file mode 100644
index 4eb12fd6af..0000000000
--- a/actionpack/test/fixtures/test/render_explicit_html_template.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.call "document.write", render(:partial => "one.html.erb")
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs b/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs
deleted file mode 100644
index 3d68041756..0000000000
--- a/actionpack/test/fixtures/test/render_implicit_html_template.js.rjs
+++ /dev/null
@@ -1 +0,0 @@
-page.call "document.write", render(:partial => "one")
diff --git a/actionpack/test/fixtures/test/streaming.erb b/actionpack/test/fixtures/test/streaming.erb
new file mode 100644
index 0000000000..fb9b8b1ade
--- /dev/null
+++ b/actionpack/test/fixtures/test/streaming.erb
@@ -0,0 +1,3 @@
+<%- provide :header do -%>Yes, <%- end -%>
+this works
+<%- content_for :footer, " like a charm" -%>
diff --git a/actionpack/test/fixtures/test/streaming_buster.erb b/actionpack/test/fixtures/test/streaming_buster.erb
new file mode 100644
index 0000000000..4221d56dcc
--- /dev/null
+++ b/actionpack/test/fixtures/test/streaming_buster.erb
@@ -0,0 +1,3 @@
+<%= yield :foo -%>
+This won't look
+<% provide :unknown, " good." -%>
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 1bf748af14..4a93def5a8 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -477,15 +477,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
- def test_env_asset_path
- @controller.config.asset_path = "/assets%s"
- def @controller.env; @_env ||= {} end
- @controller.env["action_dispatch.asset_path"] = "/omg%s"
-
- expected_path = "/assets/omg/images/rails.png"
- assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
- end
-
def test_proc_asset_id
@controller.config.asset_path = Proc.new do |asset_path|
"/assets.v12345#{asset_path}"
@@ -495,20 +486,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
- def test_env_proc_asset_path
- @controller.config.asset_path = Proc.new do |asset_path|
- "/assets.v12345#{asset_path}"
- end
-
- def @controller.env; @_env ||= {} end
- @controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path|
- "/omg#{asset_path}"
- end
-
- expected_path = "/assets.v12345/omg/images/rails.png"
- assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
- end
-
def test_image_tag_interpreting_email_cid_correctly
# An inline image has no need for an alt tag to be automatically generated from the cid:
assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid")
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 03050485fa..a9a36e6e6b 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase
def setup
super
@av = ActionView::Base.new
- @_content_for = Hash.new {|h,k| h[k] = "" }
+ @_view_flow = ActionView::OutputFlow.new
end
def test_capture_captures_the_temporary_output_buffer_in_its_block
@@ -45,6 +45,20 @@ class CaptureHelperTest < ActionView::TestCase
assert ! content_for?(:something_else)
end
+ def test_provide
+ assert !content_for?(:title)
+ provide :title, "hi"
+ assert content_for?(:title)
+ assert_equal "hi", @_view_flow.get(:title)
+ provide :title, "<p>title</p>"
+ assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @_view_flow.get(:title)
+
+ @_view_flow = ActionView::OutputFlow.new
+ provide :title, "hi"
+ provide :title, "<p>title</p>".html_safe
+ assert_equal "hi<p>title</p>", @_view_flow.get(:title)
+ end
+
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument
assert_nil @av.output_buffer
buffer = @av.with_output_buffer do
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index fd1f824a39..12d2410f49 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -53,12 +53,12 @@ class DateHelperTest < ActionView::TestCase
assert_equal "about 2 hours", distance_of_time_in_words(from, to + 89.minutes + 30.seconds)
assert_equal "about 24 hours", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 29.seconds)
- # 1440..2529
+ # 1440..2519
assert_equal "1 day", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 30.seconds)
assert_equal "1 day", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 29.seconds)
- # 2530..43199
- assert_equal "2 days", distance_of_time_in_words(from, to + 42.hours + 59.minutes + 30.seconds)
+ # 2520..43199
+ assert_equal "2 days", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 30.seconds)
assert_equal "3 days", distance_of_time_in_words(from, to + 2.days + 12.hours)
assert_equal "30 days", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 29.seconds)
diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb
index d1891094e8..30f6d1a213 100644
--- a/actionpack/test/template/erb_util_test.rb
+++ b/actionpack/test/template/erb_util_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class ErbUtilTest < Test::Unit::TestCase
include ERB::Util
@@ -29,7 +30,7 @@ class ErbUtilTest < Test::Unit::TestCase
def test_rest_in_ascii
(0..127).to_a.map {|int| int.chr }.each do |chr|
- next if %w(& " < >).include?(chr)
+ next if chr.in?('&"<>')
assert_equal chr, html_escape(chr)
end
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index ff183d097d..7afab3179c 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'controller/fake_models'
+require 'active_support/core_ext/object/inclusion'
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
@@ -1743,7 +1744,7 @@ class FormHelperTest < ActionView::TestCase
def snowman(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if (method && !['get','post'].include?(method.to_s))
+ if method && !method.to_s.in?(['get', 'post'])
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
txt << %{</div>}
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 93ff7ba0fd..b92e1d9890 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'tzinfo'
+require 'active_support/core_ext/object/inclusion'
class Map < Hash
def category
@@ -82,7 +83,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_collection_options_with_proc_for_disabled
assert_dom_equal(
"<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe went home</option>",
- options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => lambda{|p| %w(Babe Cabe).include? p.author_name })
+ options_from_collection_for_select(dummy_posts, "author_name", "title", :disabled => lambda{|p| p.author_name.in?(["Babe", "Cabe"]) })
)
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index f8671f2980..656fa0356b 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class FormTagHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormTagHelper
@@ -13,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if (method && !['get','post'].include?(method.to_s))
+ if method && !method.to_s.in?(['get','post'])
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
txt << %{</div>}
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 8aa2730da1..538e0e9874 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -35,20 +35,6 @@ class JavaScriptHelperTest < ActionView::TestCase
button_to_function("Greeting", "alert('Hello world!')")
end
- def test_button_to_function_with_rjs_block
- html = button_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<input type="button" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);;" value="Greet me!" />), html
- end
-
- def test_button_to_function_with_rjs_block_and_options
- html = button_to_function( "Greet me!", :class => "greeter" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<input type="button" class="greeter" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C\/h1\\u003E&quot;);;" value="Greet me!" />), html
- end
-
def test_button_to_function_with_onclick
assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />",
button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')")
@@ -69,34 +55,11 @@ class JavaScriptHelperTest < ActionView::TestCase
link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
end
- def test_link_to_function_with_rjs_block
- html = link_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
- def test_link_to_function_with_rjs_block_and_options
- html = link_to_function( "Greet me!", :class => "updater" ) do |page|
- page.replace_html 'header', "<h1>Greetings</h1>"
- end
- assert_dom_equal %(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
- def test_link_to_function_with_href
+ def test_function_with_href
assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
end
- def test_link_to_function_with_inner_block_does_not_raise_exception
- html = link_to_function( "Greet me!" ) do |page|
- page.replace_html 'header', (content_tag :h1 do
- 'Greetings'
- end)
- end
- assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
- end
-
def test_javascript_tag
self.output_buffer = 'foo'
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 8d063e66b0..ff94cba59f 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -180,16 +180,6 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
-
- test "data can be stored in cached templates" do
- template = @lookup_context.find("hello_world", %w(test))
- template.data["cached"] = "data"
- assert_equal "Hello world!", template.source
-
- template = @lookup_context.find("hello_world", %w(test))
- assert_equal "data", template.data["cached"]
- assert_equal "Hello world!", template.source
- end
end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
@@ -235,21 +225,6 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
template = @lookup_context.find("foo", %w(test), true)
assert_equal "Foo", template.source
end
-
- test "data can be stored as long as template was not updated" do
- template = @lookup_context.find("foo", %w(test), true)
- template.data["cached"] = "data"
- assert_equal "Foo", template.source
-
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "data", template.data["cached"]
- assert_equal "Foo", template.source
-
- @resolver.hash["test/_foo.erb"][1] = Time.now.utc
- template = @lookup_context.find("foo", %w(test), true)
- assert_nil template.data["cached"]
- assert_equal "Foo", template.source
- end
end
class TestMissingTemplate < ActiveSupport::TestCase
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
deleted file mode 100644
index a6aa848a00..0000000000
--- a/actionpack/test/template/prototype_helper_test.rb
+++ /dev/null
@@ -1,476 +0,0 @@
-require 'abstract_unit'
-require 'active_model'
-
-class Bunny < Struct.new(:Bunny, :id)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- def to_key() id ? [id] : nil end
-end
-
-class Author
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- def to_key() id ? [id] : nil end
- def save; @id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new author' : "author ##{@id}"
- end
-end
-
-class Article
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- attr_reader :id
- attr_reader :author_id
- def to_key() id ? [id] : nil end
- def save; @id = 1; @author_id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new article' : "article ##{@id}"
- end
-end
-
-class Author::Nested < Author; end
-
-
-class PrototypeHelperBaseTest < ActionView::TestCase
- attr_accessor :formats, :output_buffer
-
- def update_details(details)
- @details = details
- yield if block_given?
- end
-
- def setup
- super
- @template = self
- end
-
- def url_for(options)
- if options.is_a?(String)
- options
- else
- url = "http://www.example.com/"
- url << options[:action].to_s if options and options[:action]
- url << "?a=#{options[:a]}" if options && options[:a]
- url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
- url
- end
- end
-
- protected
- def request_forgery_protection_token
- nil
- end
-
- def protect_against_forgery?
- false
- end
-
- def create_generator
- block = Proc.new { |*args| yield(*args) if block_given? }
- JavaScriptGenerator.new self, &block
- end
-end
-
-class PrototypeHelperTest < PrototypeHelperBaseTest
- def _evaluate_assigns_and_ivars() end
-
- def setup
- @record = @author = Author.new
- @article = Article.new
- super
- end
-
- def test_update_page
- old_output_buffer = output_buffer
-
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal create_generator(&block).to_s, update_page(&block)
-
- assert_equal old_output_buffer, output_buffer
- end
-
- def test_update_page_tag
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
- end
-
- def test_update_page_tag_with_html_options
- block = Proc.new { |page| page.replace_html('foo', 'bar') }
- assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
- end
-
- def test_remote_function
- res = remote_function(:url => authors_path, :with => "'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')")
- assert_equal "new Ajax.Request('/authors', {asynchronous:true, evalScripts:true, parameters:'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')})", res
- assert res.html_safe?
- end
-
- protected
- def author_path(record)
- "/authors/#{record.id}"
- end
-
- def authors_path
- "/authors"
- end
-
- def author_articles_path(author)
- "/authors/#{author.id}/articles"
- end
-
- def author_article_path(author, article)
- "/authors/#{author.id}/articles/#{article.id}"
- end
-end
-
-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
-
- def test_insert_html_with_string
- assert_equal 'Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });',
- @generator.insert_html(:top, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { bottom: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { before: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:before, 'element', '<p>This is a test</p>')
- assert_equal 'Element.insert("element", { after: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
- @generator.insert_html(:after, 'element', '<p>This is a test</p>')
- end
-
- def test_replace_html_with_string
- assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
- @generator.replace_html('element', '<p>This is a test</p>')
- end
-
- def test_replace_element_with_string
- assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");',
- @generator.replace('element', '<div id="element"><p>This is a test</p></div>')
- end
-
- def test_remove
- assert_equal 'Element.remove("foo");',
- @generator.remove('foo')
- assert_equal '["foo","bar","baz"].each(Element.remove);',
- @generator.remove('foo', 'bar', 'baz')
- end
-
- def test_show
- assert_equal 'Element.show("foo");',
- @generator.show('foo')
- assert_equal '["foo","bar","baz"].each(Element.show);',
- @generator.show('foo', 'bar', 'baz')
- end
-
- def test_hide
- assert_equal 'Element.hide("foo");',
- @generator.hide('foo')
- assert_equal '["foo","bar","baz"].each(Element.hide);',
- @generator.hide('foo', 'bar', 'baz')
- end
-
- def test_toggle
- assert_equal 'Element.toggle("foo");',
- @generator.toggle('foo')
- assert_equal '["foo","bar","baz"].each(Element.toggle);',
- @generator.toggle('foo', 'bar', 'baz')
- end
-
- def test_alert
- assert_equal 'alert("hello");', @generator.alert('hello')
- end
-
- def test_redirect_to
- assert_equal 'window.location.href = "http://www.example.com/welcome";',
- @generator.redirect_to(:action => 'welcome')
- assert_equal 'window.location.href = "http://www.example.com/welcome?a=b&c=d";',
- @generator.redirect_to("http://www.example.com/welcome?a=b&c=d")
- end
-
- def test_reload
- assert_equal 'window.location.reload();',
- @generator.reload
- end
-
- def test_delay
- @generator.delay(20) do
- @generator.hide('foo')
- end
-
- assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
- end
-
- def test_to_s
- @generator.insert_html(:top, 'element', '<p>This is a test</p>')
- @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
- @generator.remove('foo', 'bar')
- @generator.replace_html('baz', '<p>This is a test</p>')
-
- assert_equal <<-EOS.chomp, @generator.to_s
-Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
-Element.insert("element", { bottom: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
-["foo","bar"].each(Element.remove);
-Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
- EOS
- end
-
- def test_element_access
- assert_equal %($("hello");), @generator['hello']
- end
-
- def test_element_access_on_records
- assert_equal %($("bunny_5");), @generator[Bunny.new(:id => 5)]
- assert_equal %($("new_bunny");), @generator[Bunny.new]
- end
-
- def test_element_proxy_one_deep
- @generator['hello'].hide
- assert_equal %($("hello").hide();), @generator.to_s
- end
-
- def test_element_proxy_variable_access
- @generator['hello']['style']
- assert_equal %($("hello").style;), @generator.to_s
- end
-
- def test_element_proxy_variable_access_with_assignment
- @generator['hello']['style']['color'] = 'red'
- assert_equal %($("hello").style.color = "red";), @generator.to_s
- end
-
- def test_element_proxy_assignment
- @generator['hello'].width = 400
- assert_equal %($("hello").width = 400;), @generator.to_s
- end
-
- def test_element_proxy_two_deep
- @generator['hello'].hide("first").clean_whitespace
- assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
- end
-
- def test_select_access
- assert_equal %($$("div.hello");), @generator.select('div.hello')
- end
-
- def test_select_proxy_one_deep
- @generator.select('p.welcome b').first.hide
- assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
- end
-
- def test_visual_effect
- assert_equal %(new Effect.Puff("blah",{});),
- @generator.visual_effect(:puff,'blah')
- end
-
- def test_visual_effect_toggle
- assert_equal %(Effect.toggle("blah",'appear',{});),
- @generator.visual_effect(:toggle_appear,'blah')
- end
-
- def test_sortable
- assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
- @generator.sortable('blah', :url => { :action => "order" })
- assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
- @generator.sortable('blah', :url => { :action => "order" }, :type => :synchronous)
- end
-
- def test_draggable
- assert_equal %(new Draggable("blah", {});),
- @generator.draggable('blah')
- end
-
- def test_drop_receiving
- assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
- @generator.drop_receiving('blah', :url => { :action => "order" })
- assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
- @generator.drop_receiving('blah', :url => { :action => "order" }, :type => :synchronous)
- end
-
- def test_collection_first_and_last
- @generator.select('p.welcome b').first.hide()
- @generator.select('p.welcome b').last.show()
- assert_equal <<-EOS.strip, @generator.to_s
-$$("p.welcome b").first().hide();
-$$("p.welcome b").last().show();
- EOS
- end
-
- def test_collection_proxy_with_each
- @generator.select('p.welcome b').each do |value|
- value.remove_class_name 'selected'
- end
- @generator.select('p.welcome b').each do |value, index|
- @generator.visual_effect :highlight, value
- end
- assert_equal <<-EOS.strip, @generator.to_s
-$$("p.welcome b").each(function(value, index) {
-value.removeClassName("selected");
-});
-$$("p.welcome b").each(function(value, index) {
-new Effect.Highlight(value,{});
-});
- EOS
- end
-
- def test_collection_proxy_on_collect
- @generator.select('p').collect('a') { |para| para.show }
- @generator.select('p').collect { |para| para.hide }
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").collect(function(value, index) {
-return value.show();
-});
-$$("p").collect(function(value, index) {
-return value.hide();
-});
- EOS
- @generator = create_generator
- end
-
- def test_collection_proxy_with_grep
- @generator.select('p').grep 'a', /^a/ do |value|
- @generator << '(value.className == "welcome")'
- end
- @generator.select('p').grep 'b', /b$/ do |value, index|
- @generator.call 'alert', value
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").grep(/^a/, function(value, index) {
-return (value.className == "welcome");
-});
-var b = $$("p").grep(/b$/, function(value, index) {
-alert(value);
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_inject
- @generator.select('p').inject 'a', [] do |memo, value|
- @generator << '(value.className == "welcome")'
- end
- @generator.select('p').inject 'b', nil do |memo, value, index|
- @generator.call 'alert', memo
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").inject([], function(memo, value, index) {
-return (value.className == "welcome");
-});
-var b = $$("p").inject(null, function(memo, value, index) {
-alert(memo);
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_pluck
- @generator.select('p').pluck('a', 'className')
- assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
- end
-
- def test_collection_proxy_with_zip
- ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
- ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
- @generator.call 'array.reverse'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = [1, 2, 3].zip([4,5,6], [7,8,9]);
-var b = [1, 2, 3].zip([4,5,6], [7,8,9], function(array) {
-return array.reverse();
-});
- EOS
- end
-
- def test_collection_proxy_with_find_all
- @generator.select('p').find_all 'a' do |value, index|
- @generator << '(value.className == "welcome")'
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").findAll(function(value, index) {
-return (value.className == "welcome");
-});
- EOS
- end
-
- def test_collection_proxy_with_in_groups_of
- @generator.select('p').in_groups_of('a', 3)
- @generator.select('p').in_groups_of('a', 3, 'x')
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").inGroupsOf(3);
-var a = $$("p").inGroupsOf(3, "x");
- EOS
- end
-
- def test_collection_proxy_with_each_slice
- @generator.select('p').each_slice('a', 3)
- @generator.select('p').each_slice('a', 3) do |group, index|
- group.reverse
- end
-
- assert_equal <<-EOS.strip, @generator.to_s
-var a = $$("p").eachSlice(3);
-var a = $$("p").eachSlice(3, function(value, index) {
-return value.reverse();
-});
- EOS
- end
-
- def test_debug_rjs
- ActionView::Base.debug_rjs = true
- @generator['welcome'].replace_html 'Welcome'
- assert_equal "try {\n$(\"welcome\").update(\"Welcome\");\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('$(\\\"welcome\\\").update(\\\"Welcome\\\");'); throw e }", @generator.to_s
- ensure
- ActionView::Base.debug_rjs = false
- end
-
- def test_literal
- literal = @generator.literal("function() {}")
- assert_equal "function() {}", ActiveSupport::JSON.encode(literal)
- assert_equal "", @generator.to_s
- end
-
- def test_class_proxy
- @generator.form.focus('my_field')
- assert_equal "Form.focus(\"my_field\");", @generator.to_s
- end
-
- def test_call_with_block
- @generator.call(:before)
- @generator.call(:my_method) do |p|
- p[:one].show
- p[:two].hide
- end
- @generator.call(:in_between)
- @generator.call(:my_method_with_arguments, true, "hello") do |p|
- p[:three].visual_effect(:highlight)
- end
- assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
- end
-
- def test_class_proxy_call_with_block
- @generator.my_object.my_method do |p|
- p[:one].show
- p[:two].hide
- end
- assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
- end
-end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index dd86bfed04..d4e912c410 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -68,10 +68,6 @@ module RenderTestCases
assert_equal "The secret is in the sauce\n", @view.render(:file => "test/dot.directory/render_file_with_ivar")
end
- def test_render_update
- assert_equal 'alert("Hello, World!");', @view.render(:update) { |page| page.alert('Hello, World!') }
- end
-
def test_render_partial_from_default
assert_equal "only partial", @view.render("test/partial_only")
end
@@ -231,20 +227,11 @@ module RenderTestCases
"@output_buffer << 'source: #{template.source.inspect}'\n"
end
- WithViewHandler = lambda do |template, view|
- %'"#{template.class} #{view.class}"'
- end
-
def test_render_inline_with_render_from_to_proc
ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
assert_equal '3', @view.render(:inline => "(1 + 2).to_s", :type => :ruby_handler)
end
- def test_render_inline_with_template_handler_with_view
- ActionView::Template.register_template_handler :with_view, WithViewHandler
- assert_equal 'ActionView::Template ActionView::Base', @view.render(:inline => "Hello, World!", :type => :with_view)
- end
-
def test_render_inline_with_compilable_custom_type
ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo)
diff --git a/actionpack/test/template/scriptaculous_helper_test.rb b/actionpack/test/template/scriptaculous_helper_test.rb
deleted file mode 100644
index 233012bfdd..0000000000
--- a/actionpack/test/template/scriptaculous_helper_test.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'abstract_unit'
-
-class ScriptaculousHelperTest < ActionView::TestCase
- tests ActionView::Helpers::ScriptaculousHelper
-
- def url_for(options)
- url = "http://www.example.com/"
- url << options[:action].to_s if options and options[:action]
- url
- end
-
- def test_effect
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
- assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)
- assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
- assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
- assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
- assert_equal "new Effect.Highlight(\"status\",{endcolor:'#EEEEEE'});", visual_effect(:highlight, 'status', :endcolor => '#EEEEEE')
- assert_equal "new Effect.Highlight(\"status\",{restorecolor:'#500000', startcolor:'#FEFEFE'});", visual_effect(:highlight, 'status', :restorecolor => '#500000', :startcolor => '#FEFEFE')
-
- # chop the queue params into a comma separated list
- beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
- ve = [
- visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
- visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
- visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
- ].collect { |v| v[beginning.length..-ending.length-1].split(',') }
-
- assert ve[0].include?("limit:2")
- assert ve[0].include?("scope:'test'")
- assert ve[0].include?("position:'end'")
-
- assert ve[1].include?("limit:2")
- assert ve[1].include?("scope:'list'")
-
- assert ve[2].include?("limit:2")
- assert ve[2].include?("scope:'test'")
- assert ve[2].include?("position:'end'")
- end
-
- def test_toggle_effects
- assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear, "posts")
- assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect(:toggle_slide, "posts")
- assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect(:toggle_blind, "posts")
- assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
- assert_equal "Effect.toggle(\"posts\",'slide',{});", visual_effect("toggle_slide", "posts")
- assert_equal "Effect.toggle(\"posts\",'blind',{});", visual_effect("toggle_blind", "posts")
- end
-
-
- def test_sortable_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
- sortable_element("mylist", :url => { :action => "order" })
- assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}, tag:'div'})\n//]]>\n</script>),
- sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
- assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>|,
- sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>),
- sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
- end
-
- def test_draggable_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
- draggable_element("product_13")
- assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
- draggable_element("product_13", :revert => true)
- end
-
- def test_drop_receiving_element
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1")
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => 'products')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
- drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
- assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add("droptarget1", {hoverclass:'dropready', onDrop:function(element){if (confirm('Are you sure?')) { new Ajax.Request('http://www.example.com/update_drop', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)}); }}})\n//]]>\n</script>),
- drop_receiving_element('droptarget1', :hoverclass=>'dropready', :url=>{:action=>'update_drop'}, :confirm => 'Are you sure?')
-
- end
- def protect_against_forgery?
- false
- end
-end
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
new file mode 100644
index 0000000000..67aee86d02
--- /dev/null
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -0,0 +1,96 @@
+require 'abstract_unit'
+require 'sprockets'
+
+class SprocketsHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::SprocketsHelper
+
+ attr_accessor :assets
+
+ def setup
+ super
+
+ @controller = BasicController.new
+
+ @request = Class.new do
+ def protocol() 'http://' end
+ def ssl?() false end
+ def host_with_port() 'localhost' end
+ end.new
+
+ @controller.request = @request
+
+ @assets = Sprockets::Environment.new
+ @assets.paths << FIXTURES.join("sprockets/app/javascripts")
+ @assets.paths << FIXTURES.join("sprockets/app/stylesheets")
+
+ config.perform_caching = true
+ end
+
+ def url_for(*args)
+ "http://www.example.com"
+ end
+
+ test "javascript path" do
+ assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js",
+ sprockets_javascript_path(:application)
+
+ assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ sprockets_javascript_path("xmlhr")
+ assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
+ sprockets_javascript_path("dir/xmlhr.js")
+
+ assert_equal "/dir/xmlhr.js",
+ sprockets_javascript_path("/dir/xmlhr")
+
+ assert_equal "http://www.railsapplication.com/js/xmlhr",
+ sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr")
+ assert_equal "http://www.railsapplication.com/js/xmlhr.js",
+ sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr.js")
+ end
+
+ test "javascript include tag" do
+ assert_equal '<script src="/assets/application-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag(:application)
+
+ assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("xmlhr")
+ assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("xmlhr.js")
+ assert_equal '<script src="http://www.railsapplication.com/xmlhr" type="text/javascript"></script>',
+ sprockets_javascript_include_tag("http://www.railsapplication.com/xmlhr")
+ end
+
+ test "stylesheet path" do
+ assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css",
+ sprockets_stylesheet_path(:application)
+
+ assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css",
+ sprockets_stylesheet_path("style")
+ assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css",
+ sprockets_stylesheet_path("dir/style.css")
+ assert_equal "/dir/style.css",
+ sprockets_stylesheet_path("/dir/style.css")
+
+ assert_equal "http://www.railsapplication.com/css/style",
+ sprockets_stylesheet_path("http://www.railsapplication.com/css/style")
+ assert_equal "http://www.railsapplication.com/css/style.css",
+ sprockets_stylesheet_path("http://www.railsapplication.com/css/style.css")
+ end
+
+ test "stylesheet link tag" do
+ assert_equal '<link href="/assets/application-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag(:application)
+
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style.css")
+
+ assert_equal '<link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("http://www.railsapplication.com/style.css")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style", :media => "all")
+ assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />',
+ sprockets_stylesheet_link_tag("style", :media => "print")
+ end
+end
diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb
new file mode 100644
index 0000000000..4d69081570
--- /dev/null
+++ b/actionpack/test/template/streaming_render_test.rb
@@ -0,0 +1,105 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_models'
+
+class TestController < ActionController::Base
+end
+
+class FiberedTest < ActiveSupport::TestCase
+ def setup
+ view_paths = ActionController::Base.view_paths
+ @assigns = { :secret => 'in the sauce' }
+ @view = ActionView::Base.new(view_paths, @assigns)
+ @controller_view = TestController.new.view_context
+ end
+
+ def buffered_render(options)
+ body = @view.render_body(options)
+ string = ""
+ body.each do |piece|
+ string << piece
+ end
+ string
+ end
+
+ def test_streaming_works
+ content = []
+ body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield")
+
+ body.each do |piece|
+ content << piece
+ end
+
+ assert_equal "<title>", content[0]
+ assert_equal "", content[1]
+ assert_equal "</title>\n", content[2]
+ assert_equal "Hello world!", content[3]
+ assert_equal "\n", content[4]
+ end
+
+ def test_render_file
+ assert_equal "Hello world!", buffered_render(:file => "test/hello_world.erb")
+ end
+
+ def test_render_file_with_locals
+ locals = { :secret => 'in the sauce' }
+ assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals.erb", :locals => locals)
+ end
+
+ def test_render_partial
+ assert_equal "only partial", buffered_render(:partial => "test/partial_only")
+ end
+
+ def test_render_inline
+ assert_equal "Hello, World!", buffered_render(:inline => "Hello, World!")
+ end
+
+ def test_render_without_layout
+ assert_equal "Hello world!", buffered_render(:template => "test/hello_world")
+ end
+
+ def test_render_with_layout
+ assert_equal %(<title></title>\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield")
+ end
+
+ def test_render_with_layout_which_has_render_inline
+ assert_equal %(welcome\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside")
+ end
+
+ def test_render_with_layout_which_renders_another_partial
+ assert_equal %(partial html\nHello world!\n),
+ buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside")
+ end
+
+ def test_render_with_nested_layout
+ assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
+ buffered_render(:template => "test/nested_layout.erb", :layout => "layouts/yield")
+ end
+
+ def test_render_with_file_in_layout
+ assert_equal %(\n<title>title</title>\n\n),
+ buffered_render(:template => "test/layout_render_file.erb")
+ end
+
+ def test_render_with_handler_without_streaming_support
+ assert_match "<p>This is grand!</p>", buffered_render(:template => "test/hello")
+ end
+
+ def test_render_with_streaming_multiple_yields_provide_and_content_for
+ assert_equal "Yes, \nthis works\n like a charm.",
+ buffered_render(:template => "test/streaming", :layout => "layouts/streaming")
+ end
+
+ def test_render_with_streaming_with_fake_yields_and_streaming_buster
+ assert_equal "This won't look\n good.",
+ buffered_render(:template => "test/streaming_buster", :layout => "layouts/streaming")
+ end
+
+ def test_render_with_nested_streaming_multiple_yields_provide_and_content_for
+ assert_equal "?Yes, \n\nthis works\n\n? like a charm.",
+ buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming")
+ end
+
+end if defined?(Fiber) \ No newline at end of file
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 3432a02c3c..5c655d5b69 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -113,44 +113,6 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- def test_template_expire_sets_the_timestamp_to_1970
- @template = new_template("Hello", :updated_at => Time.utc(2010))
- assert_equal Time.utc(2010), @template.updated_at
- @template.expire!
- assert_equal Time.utc(1970), @template.updated_at
- end
-
- def test_template_rerender_renders_a_template_like_self
- @template = new_template("Hello", :virtual_path => "test/foo_bar")
- @context.expects(:render).with(:template => "test/foo_bar").returns("template")
- assert_equal "template", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_root_template_like_self
- @template = new_template("Hello", :virtual_path => "foo_bar")
- @context.expects(:render).with(:template => "foo_bar").returns("template")
- assert_equal "template", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_partial_like_self
- @template = new_template("Hello", :virtual_path => "test/_foo_bar")
- @context.expects(:render).with(:partial => "test/foo_bar").returns("partial")
- assert_equal "partial", @template.rerender(@context)
- end
-
- def test_template_rerender_renders_a_root_partial_like_self
- @template = new_template("Hello", :virtual_path => "_foo_bar")
- @context.expects(:render).with(:partial => "foo_bar").returns("partial")
- assert_equal "partial", @template.rerender(@context)
- end
-
- def test_rerender_raises_an_error_without_virtual_path
- @template = new_template("Hello", :virtual_path => nil)
- assert_raise RuntimeError do
- @template.rerender(@context)
- end
- end
-
if "ruby".encoding_aware?
def test_resulting_string_is_utf8
@template = new_template
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index d0d4286393..a4fcff5167 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -315,14 +315,20 @@ class TextHelperTest < ActionView::TestCase
end
end
- def test_auto_link_should_be_html_safe
+ def test_auto_link_should_not_be_html_safe
email_raw = 'santiago@wyeworks.com'
link_raw = 'http://www.rubyonrails.org'
- assert auto_link(nil).html_safe?
- assert auto_link('').html_safe?
- assert auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?
- assert auto_link("hello #{email_raw}").html_safe?
+ assert !auto_link(nil).html_safe?, 'should not be html safe'
+ assert !auto_link('').html_safe?, 'should not be html safe'
+ assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe'
+ assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe'
+ end
+
+ def test_auto_link_email_address
+ email_raw = 'aaron@tenderlovemaking.com'
+ email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
+ assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe'
end
def test_auto_link
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 3082c7186a..03287fac7a 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,14 @@
*Rails 3.1.0 (unreleased)*
+* Add support for proc or lambda as an option for InclusionValidator,
+ ExclusionValidator, and FormatValidator [Prem Sichanugrist]
+
+ You can now supply Proc, lambda, or anything that respond to #call in those
+ validations, and it will be called with current record as an argument.
+ That given proc or lambda must returns an object which respond to #include? for
+ InclusionValidator and ExclusionValidator, and returns a regular expression
+ object for FormatValidator.
+
* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]
* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index a479795d51..5ede78617a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -8,7 +8,7 @@ module ActiveModel
# Provides a way to track changes in your object in the same way as
# Active Record does.
#
- # The requirements to implement ActiveModel::Dirty are to:
+ # The requirements for implementing ActiveModel::Dirty are:
#
# * <tt>include ActiveModel::Dirty</tt> in your object
# * Call <tt>define_attribute_methods</tt> passing each method you want to
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 5e3cf510b0..22ca3efa2b 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -278,19 +278,18 @@ module ActiveModel
# When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
# <tt>class Admin < User; end</tt> and you wanted the translation for
- # the <tt>:blank</tt> error +message+ for the <tt>title</tt> +attribute+,
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
# it looks for these translations:
#
- # <ol>
- # <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
- # <li><tt>activemodel.errors.models.admin.blank</tt></li>
- # <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
- # <li><tt>activemodel.errors.models.user.blank</tt></li>
- # <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
- # <li><tt>activemodel.errors.messages.blank</tt></li>
- # <li><tt>errors.attributes.title.blank</tt></li>
- # <li><tt>errors.messages.blank</tt></li>
- # </ol>
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
+ # * <tt>activemodel.errors.models.admin.blank</tt>
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
+ # * <tt>activemodel.errors.models.user.blank</tt>
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
+ # * <tt>activemodel.errors.messages.blank</tt>
+ # * <tt>errors.attributes.title.blank</tt>
+ # * <tt>errors.messages.blank</tt>
+ #
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 957d0ddaaa..ee94ad66cf 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -31,11 +31,10 @@ module ActiveModel
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
attr_reader :password
- attr_accessor :password_confirmation
validates_confirmation_of :password
validates_presence_of :password_digest
-
+
include InstanceMethodsOnActivation
if respond_to?(:attributes_protected_by_default)
@@ -59,7 +58,9 @@ module ActiveModel
# Encrypts the password into the password_digest attribute.
def password=(unencrypted_password)
@password = unencrypted_password
- self.password_digest = BCrypt::Password.create(unencrypted_password)
+ unless unencrypted_password.blank?
+ self.password_digest = BCrypt::Password.create(unencrypted_password)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index e38e565d09..a85c23f725 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,18 +1,35 @@
+require 'active_support/core_ext/range.rb'
+
module ActiveModel
# == Active Model Exclusion Validator
module Validations
class ExclusionValidator < EachValidator
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
def check_validity!
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
+ unless [:include?, :call].any? { |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
end
def validate_each(record, attribute, value)
- if options[:in].include?(value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ if exclusions.send(inclusion_method(exclusions), value)
record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
end
end
+
+ private
+
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
+ # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
+ # uses the previous logic of comparing a value with the range endpoints.
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
+ end
end
module HelperMethods
@@ -22,10 +39,14 @@ module ActiveModel
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
+ # validates_exclusion_of :password, :in => lambda { |p| [p.username, p.first_name] }, :message => "should not be the same as your username or first name"
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of.
+ # This can be supplied as a proc or lambda which returns an enumerable. If the enumerable
+ # is a range the test is performed with <tt>Range#cover?</tt>
+ # (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 541f53a834..6f23d492eb 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -4,10 +4,12 @@ module ActiveModel
module Validations
class FormatValidator < EachValidator
def validate_each(record, attribute, value)
- if options[:with] && value.to_s !~ options[:with]
- record.errors.add(attribute, :invalid, options.except(:with).merge!(:value => value))
- elsif options[:without] && value.to_s =~ options[:without]
- record.errors.add(attribute, :invalid, options.except(:without).merge!(:value => value))
+ if options[:with]
+ regexp = option_call(record, :with)
+ record_error(record, attribute, :with, value) if value.to_s !~ regexp
+ elsif options[:without]
+ regexp = option_call(record, :without)
+ record_error(record, attribute, :without, value) if value.to_s =~ regexp
end
end
@@ -16,12 +18,25 @@ module ActiveModel
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
+ check_options_validity(options, :with)
+ check_options_validity(options, :without)
+ end
+
+ private
- if options[:without] && !options[:without].is_a?(Regexp)
- raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
+ def option_call(record, name)
+ option = options[name]
+ option.respond_to?(:call) ? option.call(record) : option
+ end
+
+ def record_error(record, attribute, name, value)
+ record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
+ end
+
+ def check_options_validity(options, name)
+ option = options[name]
+ if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
+ raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
end
end
end
@@ -40,17 +55,26 @@ module ActiveModel
# validates_format_of :email, :without => /NOSPAM/
# end
#
+ # You can also provide a proc or lambda which will determine the regular expression that will be used to validate the attribute
+ #
+ # class Person < ActiveRecord::Base
+ # # Admin can have number as a first letter in their screen name
+ # validates_format_of :screen_name, :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\Z/i : /\A[a-z][a-z0-9_\-]*\Z/i }
+ # end
+ #
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
- # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. In addition, both must be a regular expression,
- # or else an exception will be raised.
+ # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. In addition, both must be a regular expression
+ # or a proc or lambda, or else an exception will be raised.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:with</tt> - Regular expression that if the attribute matches will result in a successful validation.
+ # This can be provided as a proc or lambda returning regular expression which will be called at runtime.
# * <tt>:without</tt> - Regular expression that if the attribute does not match will result in a successful validation.
+ # This can be provided as a proc or lambda returning regular expression which will be called at runtime.
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
# validation contexts by default (+nil+), other options are <tt>:create</tt>
# and <tt>:update</tt>.
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 92ac940f36..d32aebeb88 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -5,20 +5,30 @@ module ActiveModel
# == Active Model Inclusion Validator
module Validations
class InclusionValidator < EachValidator
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
def check_validity!
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
+ unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
end
def validate_each(record, attribute, value)
- record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value)) unless options[:in].send(include?, value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ unless exclusions.send(inclusion_method(exclusions), value)
+ record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
+ end
end
+ private
+
# In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
# range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
# uses the previous logic of comparing a value with the range endpoints.
- def include?
- options[:in].is_a?(Range) ? :cover? : :include?
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
end
end
@@ -29,11 +39,13 @@ module ActiveModel
# validates_inclusion_of :gender, :in => %w( m f )
# validates_inclusion_of :age, :in => 0..99
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
+ # validates_inclusion_of :states, :in => lambda{ |person| STATES[person.country] }
# end
#
# Configuration options:
- # * <tt>:in</tt> - An enumerable object of available items.
- # If the enumerable is a range the test is performed with <tt>Range#cover?</tt>
+ # * <tt>:in</tt> - An enumerable object of available items. This can be
+ # supplied as a proc or lambda which returns an enumerable. If the enumerable
+ # is a range the test is performed with <tt>Range#cover?</tt>
# (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index c5ed8d22d3..5304743389 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/array/wrap'
require "active_support/core_ext/module/anonymous"
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/inclusion'
module ActiveModel #:nodoc:
@@ -67,7 +68,7 @@ module ActiveModel #:nodoc:
#
# 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)
+ # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
# end
# end
#
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 015153ec7c..9a73a5ad91 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'logger'
+require 'active_support/core_ext/object/inclusion'
class SanitizerTest < ActiveModel::TestCase
@@ -9,7 +10,7 @@ class SanitizerTest < ActiveModel::TestCase
attr_accessor :logger
def deny?(key)
- [ 'admin' ].include?(key)
+ key.in?(['admin'])
end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 4a47a7a226..c455cf57b3 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -9,6 +9,18 @@ class SecurePasswordTest < ActiveModel::TestCase
@user = User.new
end
+ test "blank password" do
+ user = User.new
+ user.password = ''
+ assert !user.valid?, 'user should be invalid'
+ end
+
+ test "nil password" do
+ user = User.new
+ user.password = nil
+ assert !user.valid?, 'user should be invalid'
+ end
+
test "password must be present" do
assert !@user.valid?
assert_equal 1, @user.errors.size
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 500a5c575f..500a5c575f 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index b6a2f88667..b6a2f88667 100644
--- a/activemodel/test/cases/serializeration/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index be9d98d644..72a383f128 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -42,4 +42,16 @@ class ExclusionValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_exclusion_of_with_lambda
+ Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+
+ p = Topic.new
+ p.title = "elephant"
+ p.author_name = "sikachu"
+ assert p.invalid?
+
+ p.title = "wasabi"
+ assert p.valid?
+ end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 6c4fb36d52..73647efea5 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -98,6 +98,30 @@ class PresenceValidationTest < ActiveModel::TestCase
assert_raise(ArgumentError) { Topic.validates_format_of(:title, :without => "clearly not a regexp") }
end
+ def test_validates_format_of_with_lambda
+ Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
+
+ p = Topic.new
+ p.title = "digit"
+ p.content = "Pixies"
+ assert p.invalid?
+
+ p.content = "1234"
+ assert p.valid?
+ end
+
+ def test_validates_format_of_without_lambda
+ Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
+
+ p = Topic.new
+ p.title = "characters"
+ p.content = "1234"
+ assert p.invalid?
+
+ p.content = "Pixies"
+ assert p.valid?
+ end
+
def test_validates_format_of_for_ruby_class
Person.validates_format_of :karma, :with => /\A\d+\Z/
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 62f2ec785d..92c473de0e 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -74,4 +74,16 @@ class InclusionValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_inclusion_of_with_lambda
+ Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+
+ p = Topic.new
+ p.title = "wasabi"
+ p.author_name = "sikachu"
+ assert p.invalid?
+
+ p.title = "elephant"
+ assert p.valid?
+ end
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index e536d2b408..93eb42a52c 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,57 @@
*Rails 3.1.0 (unreleased)*
+* Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your
+ scope to be lazily evaluated, or takes parameters, please define it as a normal class method
+ instead. For example, change this:
+
+ class Post < ActiveRecord::Base
+ scope :unpublished, lambda { where('published_at > ?', Time.now) }
+ end
+
+ To this:
+
+ class Post < ActiveRecord::Base
+ def self.unpublished
+ where('published_at > ?', Time.now)
+ end
+ end
+
+ [Jon Leighton]
+
+* Default scopes are now evaluated at the latest possible moment, to avoid problems where
+ scopes would be created which would implicitly contain the default scope, which would then
+ be impossible to get rid of via Model.unscoped.
+
+ Note that this means that if you are inspecting the internal structure of an
+ ActiveRecord::Relation, it will *not* contain the default scope, though the resulting
+ query will do. You can get a relation containing the default scope by calling
+ ActiveRecord#with_default_scope, though this is not part of the public API.
+
+ [Jon Leighton]
+
+* Deprecated support for passing hashes and relations to 'default_scope'. Please create a class
+ method for your scope instead. For example, change this:
+
+ class Post < ActiveRecord::Base
+ default_scope where(:published => true)
+ end
+
+ To this:
+
+ class Post < ActiveRecord::Base
+ def self.default_scope
+ where(:published => true)
+ end
+ end
+
+ Rationale: It will make the implementation simpler because we can simply use inheritance to
+ handle inheritance scenarios, rather than trying to make up our own rules about what should
+ happen when you call default_scope multiple times or in subclasses.
+
+ [Jon Leighton]
+
+* PostgreSQL adapter only supports PostgreSQL version 8.2 and higher.
+
* ConnectionManagement middleware is changed to clean up the connection pool
after the rack body has been flushed.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 27c446b12c..687b668634 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module Associations
@@ -163,7 +164,7 @@ module ActiveRecord
def creation_attributes
attributes = {}
- if [:has_one, :has_many].include?(reflection.macro) && !options[:through]
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
if reflection.options[:as]
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 964e7fddc8..f6d26840c2 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
self.macro = :belongs_to
@@ -65,7 +67,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete])
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 77bb66228d..ecbc70888f 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
self.macro = :has_many
@@ -14,7 +16,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete_all, :nullify, :restrict].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete_all, :nullify, :restrict])
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \
":nullify or :restrict (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 07ba5d088e..88c0d3e90f 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
self.macro = :has_one
@@ -27,7 +29,7 @@ module ActiveRecord::Associations::Builder
def configure_dependency
if options[:dependent]
- unless [:destroy, :delete, :nullify, :restrict].include?(options[:dependent])
+ unless options[:dependent].in?([:destroy, :delete, :nullify, :restrict])
raise ArgumentError, "The :dependent option expects either :destroy, :delete, " \
":nullify or :restrict (#{options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 9f4fc44cc6..33a184d48d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -21,14 +21,7 @@ module ActiveRecord
attr_reader :proxy
def initialize(owner, reflection)
- # When scopes are created via method_missing on the proxy, they are stored so that
- # any records fetched from the database are kept around for future use.
- @scopes_cache = Hash.new do |hash, method|
- hash[method] = { }
- end
-
super
-
@proxy = CollectionProxy.new(self)
end
@@ -74,7 +67,6 @@ module ActiveRecord
def reset
@loaded = false
@target = []
- @scopes_cache.clear
end
def select(select = nil)
@@ -327,10 +319,6 @@ module ActiveRecord
end
end
- def cached_scope(method, args)
- @scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args)
- end
-
def load_target
if find_target?
targets = []
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index cf77d770c9..388173c1fb 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -82,8 +82,6 @@ module ActiveRecord
end
end
- elsif @association.klass.scopes[method]
- @association.cached_scope(method, args)
else
scoped.readonly(nil).send(method, *args, &block)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 1d2e8667e4..7134dc85c8 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
@@ -50,7 +52,7 @@ module ActiveRecord
end
def remove_target!(method)
- if [:delete, :destroy].include?(method)
+ if method.in?([:delete, :destroy])
target.send(method)
else
nullify_owner_attributes(target)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 4121a5b378..0a666598ed 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -89,14 +89,7 @@ module ActiveRecord
foreign_key = reflection.active_record_primary_key
end
- constraint = table[key].eq(foreign_table[foreign_key])
-
- if reflection.klass.finder_needs_type_condition?
- constraint = table.create_and([
- constraint,
- reflection.klass.send(:type_condition, table)
- ])
- end
+ constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
relation.from(join(table, constraint))
@@ -111,6 +104,19 @@ module ActiveRecord
relation
end
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
+ constraint = table[key].eq(foreign_table[foreign_key])
+
+ if reflection.klass.finder_needs_type_condition?
+ constraint = table.create_and([
+ constraint,
+ reflection.klass.send(:type_condition, table)
+ ])
+ end
+
+ constraint
+ end
+
def join_relation(joining_relation)
self.join_type = Arel::OuterJoin
joining_relation.joins(self)
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 69d5cd83f1..a248eb3a7b 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
if attr_name == primary_key && attr_name != "id"
- define_read_method(:id, attr_name, columns_hash[attr_name])
+ define_read_method('id', attr_name, columns_hash[attr_name])
end
end
@@ -59,7 +59,9 @@ module ActiveRecord
end
# Define an attribute reader method. Cope with nil column.
- def define_read_method(symbol, attr_name, column)
+ # method_name is the same as attr_name except when a non-standard primary key is used,
+ # we still define #id as an accessor for the key
+ def define_read_method(method_name, attr_name, column)
cast_code = column.type_cast_code('v')
access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
@@ -70,12 +72,25 @@ module ActiveRecord
if cache_attribute?(attr_name)
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
end
- if symbol =~ /^[a-zA-Z_]\w*[!?=]?$/
- generated_attribute_methods.module_eval("def _#{symbol}; #{access_code}; end; alias #{symbol} _#{symbol}", __FILE__, __LINE__)
+
+ # Where possible, generate the method by evalling a string, as this will result in
+ # faster accesses because it avoids the block eval and then string eval incurred
+ # by the second branch.
+ #
+ # The second, slower, branch is necessary to support instances where the database
+ # returns columns with extra stuff in (like 'my_column(omg)').
+ if method_name =~ /^[a-zA-Z_]\w*[!?=]?$/
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
+ def _#{method_name}
+ #{access_code}
+ end
+
+ alias #{method_name} _#{method_name}
+ STR
else
generated_attribute_methods.module_eval do
- define_method("_#{symbol}") { eval(access_code) }
- alias_method(symbol, "_#{symbol}")
+ define_method("_#{method_name}") { eval(access_code) }
+ alias_method(method_name, "_#{method_name}")
end
end
end
@@ -84,9 +99,11 @@ module ActiveRecord
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- send "_#{attr_name}"
- rescue NoMethodError
- _read_attribute attr_name
+ if respond_to? "_#{attr_name}"
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
+ else
+ _read_attribute attr_name
+ end
end
def _read_attribute(attr_name)
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 6aac96df6f..62a3cfa9a5 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
@@ -58,7 +59,7 @@ module ActiveRecord
private
def create_time_zone_conversion_attribute?(name, column)
- time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
+ time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index fe81c7dc2f..08a41e2d8b 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -425,8 +425,8 @@ module ActiveRecord #:nodoc:
self.store_full_sti_class = true
# Stores the default scope for the class
- class_attribute :default_scoping, :instance_writer => false
- self.default_scoping = []
+ class_attribute :default_scopes, :instance_writer => false
+ self.default_scopes = []
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
@@ -870,7 +870,9 @@ module ActiveRecord #:nodoc:
# Returns a scope for this class without taking into account the default_scope.
#
# class Post < ActiveRecord::Base
- # default_scope :published => true
+ # def self.default_scope
+ # where :published => true
+ # end
# end
#
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
@@ -892,13 +894,8 @@ module ActiveRecord #:nodoc:
block_given? ? relation.scoping { yield } : relation
end
- def scoped_methods #:nodoc:
- key = :"#{self}_scoped_methods"
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
- end
-
def before_remove_const #:nodoc:
- reset_scoped_methods
+ self.current_scope = nil
end
# Specifies how the record is loaded by +Marshal+.
@@ -1020,7 +1017,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, current_scoped_methods) : scoped
+ relation = options.any? ? scoped(options) : 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
@@ -1109,43 +1106,47 @@ module ActiveRecord #:nodoc:
# end
#
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
- def with_scope(method_scoping = {}, action = :merge, &block)
- method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
+ def with_scope(scope = {}, action = :merge, &block)
+ # If another Active Record class has been passed in, get its current scope
+ scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
+
+ previous_scope = self.current_scope
- if method_scoping.is_a?(Hash)
+ if scope.is_a?(Hash)
# Dup first and second level of hash (method and params).
- method_scoping = method_scoping.dup
- method_scoping.each do |method, params|
- method_scoping[method] = params.dup unless params == true
+ scope = scope.dup
+ scope.each do |method, params|
+ scope[method] = params.dup unless params == true
end
- method_scoping.assert_valid_keys([ :find, :create ])
- relation = construct_finder_arel(method_scoping[:find] || {})
+ scope.assert_valid_keys([ :find, :create ])
+ relation = construct_finder_arel(scope[:find] || {})
+ relation.default_scoped = true unless action == :overwrite
- if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
+ if previous_scope && previous_scope.create_with_value && scope[:create]
scope_for_create = if action == :merge
- current_scoped_methods.create_with_value.merge(method_scoping[:create])
+ previous_scope.create_with_value.merge(scope[:create])
else
- method_scoping[:create]
+ scope[:create]
end
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
+ scope_for_create = scope[:create]
+ scope_for_create ||= previous_scope.create_with_value if previous_scope
relation = relation.create_with(scope_for_create) if scope_for_create
end
- method_scoping = relation
+ scope = relation
end
- method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
+ scope = previous_scope.merge(scope) if previous_scope && action == :merge
- self.scoped_methods << method_scoping
+ self.current_scope = scope
begin
yield
ensure
- self.scoped_methods.pop
+ self.current_scope = previous_scope
end
end
@@ -1168,39 +1169,80 @@ MSG
with_scope(method_scoping, :overwrite, &block)
end
- # Sets the default options for the model. The format of the
- # <tt>options</tt> argument is the same as in find.
+ def current_scope #:nodoc:
+ Thread.current[:"#{self}_current_scope"]
+ end
+
+ def current_scope=(scope) #:nodoc:
+ Thread.current[:"#{self}_current_scope"] = scope
+ end
+
+ # Implement this method in your model to set a default scope for all operations on
+ # the model.
#
# class Person < ActiveRecord::Base
- # default_scope order('last_name, first_name')
+ # def self.default_scope
+ # order('last_name, first_name')
+ # end
# end
#
- # <tt>default_scope</tt> is also applied while creating/building a record. It is not
+ # Person.all # => SELECT * FROM people ORDER BY last_name, first_name
+ #
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
# applied while updating a record.
#
# class Article < ActiveRecord::Base
- # default_scope where(:published => true)
+ # def self.default_scope
+ # where(:published => true)
+ # end
# end
#
# Article.new.published # => true
# Article.create.published # => true
- def default_scope(options = {})
- reset_scoped_methods
- default_scoping = self.default_scoping.dup
- self.default_scoping = default_scoping << construct_finder_arel(options, default_scoping.pop)
- end
+ #
+ # === Deprecation warning
+ #
+ # There is an alternative syntax as follows:
+ #
+ # class Person < ActiveRecord::Base
+ # default_scope order('last_name, first_name')
+ # end
+ #
+ # This is now deprecated and will be removed in Rails 3.2.
+ def default_scope(scope = {})
+ ActiveSupport::Deprecation.warn <<-WARN
+Passing a hash or scope to default_scope is deprecated and will be removed in Rails 3.2. You should create a class method for your scope instead. For example, change this:
- def current_scoped_methods #:nodoc:
- method = scoped_methods.last
- if method.respond_to?(:call)
- relation.scoping { method.call }
- else
- method
- end
+class Post < ActiveRecord::Base
+ default_scope where(:published => true)
+end
+
+To this:
+
+class Post < ActiveRecord::Base
+ def self.default_scope
+ where(:published => true)
+ end
+end
+WARN
+
+ self.default_scopes = default_scopes.dup << scope
end
- def reset_scoped_methods #:nodoc:
- Thread.current[:"#{self}_scoped_methods"] = nil
+ def build_default_scope #:nodoc:
+ if method(:default_scope).owner != Base.singleton_class
+ # Use relation.scoping to ensure we ignore whatever the current value of
+ # self.current_scope may be.
+ relation.scoping { default_scope }
+ elsif default_scopes.any?
+ default_scopes.inject(relation) do |default_scope, scope|
+ if scope.is_a?(Hash)
+ default_scope.apply_finder_options(scope)
+ else
+ default_scope.merge(scope)
+ end
+ end
+ end
end
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -1916,11 +1958,8 @@ MSG
end
def populate_with_current_scope_attributes
- if scope = self.class.send(:current_scoped_methods)
- create_with = scope.scope_for_create
- create_with.each { |att,value|
- respond_to?("#{att}=") && send("#{att}=", value)
- }
+ self.class.scoped.scope_for_create.each do |att,value|
+ respond_to?("#{att}=") && send("#{att}=", value)
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 86d58df99b..a175bf003c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -214,6 +214,24 @@ module ActiveRecord
# needs to be aware of it because an ordinary +save+ will raise such exception
# instead of quietly returning +false+.
#
+ # == Debugging callbacks
+ #
+ # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
+ # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
+ # defines what part of the chain the callback runs in.
+ #
+ # To find all callbacks in the before_save callback chain:
+ #
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
+ #
+ # Returns an array of callback objects that form the before_save chain.
+ #
+ # To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
+ #
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
+ #
+ # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
+ #
module Callbacks
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index a3082b8f01..6d9b5c7b32 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -56,8 +56,17 @@ module ActiveRecord
end
# Returns the last auto-generated ID from the affected table.
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- insert_sql(sql, name, pk, id_value, sequence_name)
+ #
+ # +id_value+ will be returned unless the value is nil, in
+ # which case the database will attempt to calculate the last inserted
+ # id and return that value.
+ #
+ # If the next id was calculated in advance (as in Oracle), it should be
+ # passed in as +id_value+.
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds)
+ id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
@@ -364,6 +373,15 @@ module ActiveRecord
end
end
end
+
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ [sql, binds]
+ end
+
+ def last_inserted_id(result)
+ row = result.rows.first
+ row && row.first
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7489e88eef..3de850ec9e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -35,7 +35,43 @@ module ActiveRecord
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
else
- "'#{quote_string(value.to_yaml)}'"
+ "'#{quote_string(YAML.dump(value))}'"
+ end
+ end
+
+ # Cast a +value+ to a type that the database understands. For example,
+ # SQLite does not understand dates, so this method will convert a Date
+ # to a String.
+ def type_cast(value, column)
+ return value.id if value.respond_to?(:quoted_id)
+
+ case value
+ when String, ActiveSupport::Multibyte::Chars
+ value = value.to_s
+ return value unless column
+
+ case column.type
+ when :binary then value
+ when :integer then value.to_i
+ when :float then value.to_f
+ else
+ value
+ end
+
+ when true, false
+ if column && column.type == :integer
+ value ? 1 : 0
+ else
+ value ? 't' : 'f'
+ end
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when nil then nil
+ when BigDecimal then value.to_s('F')
+ when Numeric then value
+ when Date, Time then quoted_date(value)
+ when Symbol then value.to_s
+ else
+ YAML.dump(value)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0f44baa2fe..d24cce0a3c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -105,7 +105,7 @@ module ActiveRecord
# Returns a bind substitution value given a +column+ and list of current
# +binds+
- def substitute_for(column, binds)
+ def substitute_at(column, index)
Arel.sql '?'
end
@@ -203,6 +203,10 @@ module ActiveRecord
def release_savepoint
end
+ def case_sensitive_modifier(node)
+ node
+ end
+
def current_savepoint_name
"active_record_#{open_transactions}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 7bad511c64..7ac72acd58 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -282,6 +282,17 @@ module ActiveRecord
end
alias :create :insert_sql
+ def exec_insert(sql, name, binds)
+ binds = binds.dup
+
+ # Pretend to support bind parameters
+ execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ end
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
def update_sql(sql, name = nil)
super
@connection.affected_rows
@@ -528,6 +539,11 @@ module ActiveRecord
def case_sensitive_equality_operator
"= BINARY"
end
+ deprecate :case_sensitive_equality_operator
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
@@ -587,8 +603,26 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
- def select(sql, name = nil)
- execute(sql, name).each(:as => :hash)
+ def select(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).to_a
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [])
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+
+ log(sql, name, binds) do
+ begin
+ result = @connection.query(sql)
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ ActiveRecord::Result.new(result.fields, result.to_a)
+ end
end
def supports_views?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e1186209d3..c2e75acb9a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -196,6 +196,7 @@ module ActiveRecord
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
@statements = {}
+ @client_encoding = nil
connect
end
@@ -243,6 +244,12 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless value == true || value == false
+
+ value ? 1 : 0
+ end
+
def quote_column_name(name) #:nodoc:
@quoted_column_names[name] ||= "`#{name}`"
end
@@ -330,6 +337,63 @@ module ActiveRecord
@statements.clear
end
+ if "<3".respond_to?(:encode)
+ # Taken from here:
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
+ ENCODINGS = {
+ "armscii8" => nil,
+ "ascii" => Encoding::US_ASCII,
+ "big5" => Encoding::Big5,
+ "binary" => Encoding::ASCII_8BIT,
+ "cp1250" => Encoding::Windows_1250,
+ "cp1251" => Encoding::Windows_1251,
+ "cp1256" => Encoding::Windows_1256,
+ "cp1257" => Encoding::Windows_1257,
+ "cp850" => Encoding::CP850,
+ "cp852" => Encoding::CP852,
+ "cp866" => Encoding::IBM866,
+ "cp932" => Encoding::Windows_31J,
+ "dec8" => nil,
+ "eucjpms" => Encoding::EucJP_ms,
+ "euckr" => Encoding::EUC_KR,
+ "gb2312" => Encoding::EUC_CN,
+ "gbk" => Encoding::GBK,
+ "geostd8" => nil,
+ "greek" => Encoding::ISO_8859_7,
+ "hebrew" => Encoding::ISO_8859_8,
+ "hp8" => nil,
+ "keybcs2" => nil,
+ "koi8r" => Encoding::KOI8_R,
+ "koi8u" => Encoding::KOI8_U,
+ "latin1" => Encoding::ISO_8859_1,
+ "latin2" => Encoding::ISO_8859_2,
+ "latin5" => Encoding::ISO_8859_9,
+ "latin7" => Encoding::ISO_8859_13,
+ "macce" => Encoding::MacCentEuro,
+ "macroman" => Encoding::MacRoman,
+ "sjis" => Encoding::SHIFT_JIS,
+ "swe7" => nil,
+ "tis620" => Encoding::TIS_620,
+ "ucs2" => Encoding::UTF_16BE,
+ "ujis" => Encoding::EucJP_ms,
+ "utf8" => Encoding::UTF_8,
+ "utf8mb4" => Encoding::UTF_8,
+ }
+ else
+ ENCODINGS = Hash.new { |h,k| h[k] = k }
+ end
+
+ # Get the client encoding for this database
+ def client_encoding
+ return @client_encoding if @client_encoding
+
+ result = exec_query(
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
+ 'SCHEMA')
+ @client_encoding = ENCODINGS[result.rows.last.last]
+ end
+
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
result = nil
@@ -345,7 +409,7 @@ module ActiveRecord
end
stmt.execute(*binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
if metadata = stmt.result_metadata
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
@@ -363,6 +427,14 @@ module ActiveRecord
end
end
+ def exec_insert(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
+ def last_inserted_id(result)
+ @connection.insert_id
+ end
+
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
# Some queries, like SHOW CREATE TABLE don't work through the prepared
# statement API. For those queries, we need to use this method. :'(
@@ -506,7 +578,7 @@ module ActiveRecord
def tables(name = nil, database = nil) #:nodoc:
tables = []
- result = execute(["SHOW TABLES", database].compact.join(' IN '), name)
+ result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
result.each { |field| tables << field[0] }
result.free
tables
@@ -551,7 +623,7 @@ module ActiveRecord
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- result = execute(sql)
+ result = execute(sql, 'SCHEMA')
result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
@@ -638,7 +710,7 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table) #:nodoc:
keys = []
- result = execute("describe #{quote_table_name(table)}")
+ result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
result.each_hash do |h|
keys << h["Field"]if h["Key"] == "PRI"
end
@@ -655,6 +727,11 @@ module ActiveRecord
def case_sensitive_equality_operator
"= BINARY"
end
+ deprecate :case_sensitive_equality_operator
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
where_sql
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5a830a50fb..e74ec84e81 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -229,7 +229,12 @@ module ActiveRecord
@statements = {}
connect
- @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
+
+ if postgresql_version < 80200
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
+ end
+
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
def clear_cache!
@@ -293,13 +298,13 @@ module ActiveRecord
# Enable standard-conforming strings if available.
def set_standard_conforming_strings
old, self.client_min_messages = client_min_messages, 'panic'
- execute('SET standard_conforming_strings = on') rescue nil
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
ensure
self.client_min_messages = old
end
def supports_insert_with_returning?
- postgresql_version >= 80200
+ true
end
def supports_ddl_transactions?
@@ -310,10 +315,9 @@ module ActiveRecord
true
end
- # Returns the configured supported identifier length supported by PostgreSQL,
- # or report the default of 63 on PostgreSQL 7.x.
+ # Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
+ @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
end
# QUOTING ==================================================
@@ -356,6 +360,18 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless column
+
+ case value
+ when String
+ return super unless 'bytea' == column.sql_type
+ escape_bytea(value)
+ else
+ super
+ end
+ end
+
# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
@connection.escape(s)
@@ -404,7 +420,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def supports_disable_referential_integrity?() #:nodoc:
- postgresql_version >= 80100
+ true
end
def disable_referential_integrity #:nodoc:
@@ -427,34 +443,16 @@ module ActiveRecord
end
# Executes an INSERT query and returns the new record's ID
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
# Extract the table from the insert sql. Yuck.
- table = sql.split(" ", 4)[2].gsub('"', '')
-
- # Try an insert with 'returning id' if available (PG >= 8.2)
- if supports_insert_with_returning?
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
- if pk
- id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
- clear_query_cache
- return id
- end
- end
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
- # Otherwise, insert then grab last_insert_id.
- if insert_id = super
- insert_id
- else
- # If neither pk nor sequence name is given, look them up.
- unless pk || sequence_name
- pk, sequence_name = *pk_and_sequence_for(table)
- end
+ pk ||= primary_key(table)
- # If a pk is given, fallback to default sequence name.
- # Don't fetch last insert id for a table without a pk.
- if pk && sequence_name ||= default_sequence_name(table, pk)
- last_insert_id(sequence_name)
- end
+ if pk
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
+ else
+ super
end
end
alias :create :insert
@@ -525,8 +523,8 @@ module ActiveRecord
end
end
- def substitute_for(column, current_values)
- Arel.sql("$#{current_values.length + 1}")
+ def substitute_at(column, index)
+ Arel.sql("$#{index + 1}")
end
def exec_query(sql, name = 'SQL', binds = [])
@@ -544,7 +542,7 @@ module ActiveRecord
# Clear the queue
@connection.get_last_result
@connection.send_query_prepared(key, binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
@connection.block
result = @connection.get_last_result
@@ -554,6 +552,22 @@ module ActiveRecord
end
end
+ def exec_insert(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ unless pk
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
+
+ pk = primary_key(table)
+ end
+
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
+
+ [sql, binds]
+ end
+
# Executes an UPDATE query and returns the number of affected tuples.
def update_sql(sql, name = nil)
super.cmd_tuples
@@ -632,20 +646,12 @@ module ActiveRecord
# Example:
# drop_database 'matt_development'
def drop_database(name) #:nodoc:
- if postgresql_version >= 80200
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
- else
- begin
- execute "DROP DATABASE #{quote_table_name(name)}"
- rescue ActiveRecord::StatementInvalid
- @logger.warn "#{name} database doesn't exist." if @logger
- end
- end
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
- query(<<-SQL, name).map { |row| row[0] }
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
SELECT tablename
FROM pg_tables
WHERE schemaname = ANY (current_schemas(false))
@@ -653,7 +659,21 @@ module ActiveRecord
end
def table_exists?(name)
- name = name.to_s
+ schema, table = extract_schema_and_table(name.to_s)
+
+ binds = [[nil, table.gsub(/(^"|"$)/,'')]]
+ binds << [nil, schema] if schema
+
+ exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
+ SELECT COUNT(*)
+ FROM pg_tables
+ WHERE tablename = $1
+ #{schema ? "AND schemaname = $2" : ''}
+ SQL
+ end
+
+ # Extracts the table and schema name from +name+
+ def extract_schema_and_table(name)
schema, table = name.split('.', 2)
unless table # A table was provided without a schema
@@ -665,13 +685,7 @@ module ActiveRecord
table = name
schema = nil
end
-
- query(<<-SQL).first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_tables
- WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
- #{schema ? "AND schemaname = '#{schema}'" : ''}
- SQL
+ [schema, table]
end
# Returns the list of all indexes for a table.
@@ -748,37 +762,47 @@ module ActiveRecord
# Returns the current client message level.
def client_min_messages
- query('SHOW client_min_messages')[0][0]
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
end
# Set the client message level.
def client_min_messages=(level)
- execute("SET client_min_messages TO '#{level}'")
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
end
# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name(table_name, pk = nil) #:nodoc:
- default_pk, default_seq = pk_and_sequence_for(table_name)
- default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
+ serial_sequence(table_name, pk || 'id').split('.').last
+ rescue ActiveRecord::StatementInvalid
+ "#{table_name}_#{pk || 'id'}_seq"
+ end
+
+ def serial_sequence(table, column)
+ result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
+ SELECT pg_get_serial_sequence($1, $2)
+ eosql
+ result.rows.first.first
end
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
unless pk and sequence
default_pk, default_sequence = pk_and_sequence_for(table)
+
pk ||= default_pk
sequence ||= default_sequence
end
- if pk
- if sequence
- quoted_sequence = quote_column_name(sequence)
- select_value <<-end_sql, 'Reset sequence'
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
- end_sql
- else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
- end
+ if @logger && pk && !sequence
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ end
+
+ if pk && sequence
+ quoted_sequence = quote_column_name(sequence)
+
+ select_value <<-end_sql, 'Reset sequence'
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
+ end_sql
end
end
@@ -786,7 +810,7 @@ module ActiveRecord
def pk_and_sequence_for(table) #:nodoc:
# First try looking for a sequence with a dependency on the
# given table's primary key.
- result = exec_query(<<-end_sql, 'PK and serial sequence').rows.first
+ result = exec_query(<<-end_sql, 'SCHEMA').rows.first
SELECT attr.attname, seq.relname
FROM pg_class seq,
pg_attribute attr,
@@ -803,28 +827,6 @@ module ActiveRecord
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
- if result.nil? or result.empty?
- # If that fails, try parsing the primary key's default value.
- # Support the 7.x and 8.0 nextval('foo'::text) as well as
- # the 8.1+ nextval('foo'::regclass).
- result = query(<<-end_sql, 'PK and custom sequence')[0]
- SELECT attr.attname,
- CASE
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
- substr(split_part(def.adsrc, '''', 2),
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
- ELSE split_part(def.adsrc, '''', 2)
- END
- FROM pg_class t
- JOIN pg_attribute attr ON (t.oid = attrelid)
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
- AND cons.contype = 'p'
- AND def.adsrc ~* 'nextval'
- end_sql
- end
-
# [primary_key, sequence]
[result.first, result.last]
rescue
@@ -833,8 +835,21 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
+ row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
+ SELECT DISTINCT(attr.attname)
+ FROM pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE attr.attrelid = dep.refobjid
+ AND attr.attnum = dep.refobjsubid
+ AND attr.attrelid = cons.conrelid
+ AND attr.attnum = cons.conkey[1]
+ AND cons.contype = 'p'
+ AND dep.refobjid = $1::regclass
+ end_sql
+
+ row && row.first
end
# Renames a table.
@@ -848,38 +863,14 @@ module ActiveRecord
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
add_column_options!(add_column_sql, options)
- begin
- execute add_column_sql
- rescue ActiveRecord::StatementInvalid => e
- raise e if postgresql_version > 80000
-
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
- end
+ execute add_column_sql
end
# Changes the column of a table.
def change_column(table_name, column_name, type, options = {})
quoted_table_name = quote_table_name(table_name)
- begin
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- rescue ActiveRecord::StatementInvalid => e
- raise e if postgresql_version > 80000
- # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
- begin
- begin_db_transaction
- tmp_column_name = "#{column_name}_ar_tmp"
- add_column(table_name, tmp_column_name, type, options)
- execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
- remove_column(table_name, column_name)
- rename_column(table_name, tmp_column_name, column_name)
- commit_db_transaction
- rescue
- rollback_db_transaction
- end
- end
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -1031,9 +1022,9 @@ module ActiveRecord
# If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC.
if ActiveRecord::Base.default_timezone == :utc
- execute("SET time zone 'UTC'")
+ execute("SET time zone 'UTC'", 'SCHEMA')
elsif @local_tz
- execute("SET time zone '#{@local_tz}'")
+ execute("SET time zone '#{@local_tz}'", 'SCHEMA')
end
end
@@ -1076,7 +1067,7 @@ module ActiveRecord
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
- exec_query(<<-end_sql).rows
+ exec_query(<<-end_sql, 'SCHEMA').rows
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 32229a8410..9e7f874f4b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -165,7 +165,7 @@ module ActiveRecord
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
}
end
@@ -173,6 +173,14 @@ module ActiveRecord
end
end
+ def exec_insert(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
+ def last_inserted_id(result)
+ @connection.last_insert_row_id
+ end
+
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.execute(sql) }
end
@@ -188,7 +196,8 @@ module ActiveRecord
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super || @connection.last_insert_row_id
+ super
+ id_value || @connection.last_insert_row_id
end
alias :create :insert_sql
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index d523c643ba..0939ec2626 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -173,10 +173,10 @@ class FixturesFileNotFound < StandardError; end
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
# large sets of fixtured data.
#
-# = Dynamic fixtures with ERb
+# = Dynamic fixtures with ERB
#
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
-# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
#
# <% for i in 1..1000 %>
# fix_<%= i %>:
@@ -186,7 +186,7 @@ class FixturesFileNotFound < StandardError; end
#
# This will create 1000 very simple YAML fixtures.
#
-# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
+# Using ERB, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
# This is however a feature to be used with some caution. The point of fixtures are that they're
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index d18b2b0a54..95a8e5cff7 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -50,7 +50,14 @@ module ActiveRecord
def get(klass, primary_key)
obj = repository[klass.symbolized_base_class][primary_key]
- obj.is_a?(klass) ? obj : nil
+ if obj.is_a?(klass)
+ if ActiveRecord::Base.logger
+ ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map"
+ end
+ obj
+ else
+ nil
+ end
end
def add(record)
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index d291632260..f1df04950b 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -9,11 +9,6 @@ module ActiveRecord
module NamedScope
extend ActiveSupport::Concern
- included do
- class_attribute :scopes
- self.scopes = {}
- end
-
module ClassMethods
# Returns an anonymous \scope.
#
@@ -35,7 +30,13 @@ module ActiveRecord
if options
scoped.apply_finder_options(options)
else
- current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
+ if current_scope
+ current_scope.clone
+ else
+ scope = relation.clone
+ scope.default_scoped = true
+ scope
+ end
end
end
@@ -50,6 +51,14 @@ module ActiveRecord
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
#
+ # Note that this is simply 'syntactic sugar' for defining an actual class method:
+ #
+ # class Shirt < ActiveRecord::Base
+ # def self.red
+ # where(:color => 'red')
+ # end
+ # end
+ #
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
@@ -73,14 +82,34 @@ module ActiveRecord
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
# only shirts.
#
- # Named \scopes can also be procedural:
+ # If you need to pass parameters to a scope, define it as a normal method:
#
# class Shirt < ActiveRecord::Base
- # scope :colored, lambda {|color| where(:color => color) }
+ # def self.colored(color)
+ # where(:color => color)
+ # end
# end
#
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
+ # Note that scopes defined with \scope will be evaluated when they are defined, rather than
+ # when they are used. For example, the following would be incorrect:
+ #
+ # class Post < ActiveRecord::Base
+ # scope :recent, where('published_at >= ?', Time.now - 1.week)
+ # end
+ #
+ # The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
+ # class was defined, and so the resultant SQL query would always be the same. The correct
+ # way to do this would be via a class method, which will re-evaluate the scope each time
+ # it is called:
+ #
+ # class Post < ActiveRecord::Base
+ # def self.recent
+ # where('published_at >= ?', Time.now - 1.week)
+ # end
+ # end
+ #
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
@@ -91,6 +120,18 @@ module ActiveRecord
# end
# end
#
+ # The above could also be written as a class method like so:
+ #
+ # class Shirt < ActiveRecord::Base
+ # def self.red
+ # where(:color => 'red').extending do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ # end
+ #
# Scopes can also be used while creating/building a record.
#
# class Article < ActiveRecord::Base
@@ -99,34 +140,68 @@ module ActiveRecord
#
# Article.published.new.published # => true
# Article.published.create.published # => true
+ #
+ # Class methods on your model are automatically available
+ # on scopes. Assuming the following setup:
+ #
+ # class Article < ActiveRecord::Base
+ # scope :published, where(:published => true)
+ # scope :featured, where(:featured => true)
+ #
+ # def self.latest_article
+ # order('published_at desc').first
+ # end
+ #
+ # def self.titles
+ # map(&:title)
+ # end
+ #
+ # end
+ #
+ # We are able to call the methods like this:
+ #
+ # Article.published.featured.latest_article
+ # Article.featured.titles
+
def scope(name, scope_options = {})
name = name.to_sym
valid_scope_name?(name)
extension = Module.new(&Proc.new) if block_given?
+ if !scope_options.is_a?(Relation) && scope_options.respond_to?(:call)
+ ActiveSupport::Deprecation.warn <<-WARN
+Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your scope to be lazily evaluated, or takes parameters, please define it as a normal class method instead. For example, change this:
+
+class Post < ActiveRecord::Base
+ scope :unpublished, lambda { where('published_at > ?', Time.now) }
+end
+
+To this:
+
+class Post < ActiveRecord::Base
+ def self.unpublished
+ where('published_at > ?', Time.now)
+ end
+end
+ WARN
+ end
+
scope_proc = lambda do |*args|
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
+ options = scoped.apply_finder_options(options) if options.is_a?(Hash)
- relation = if options.is_a?(Hash)
- scoped.apply_finder_options(options)
- elsif options
- scoped.merge(options)
- else
- scoped
- end
+ relation = scoped.merge(options)
extension ? relation.extending(extension) : relation
end
- self.scopes = self.scopes.merge name => scope_proc
-
- singleton_class.send(:redefine_method, name, &scopes[name])
+ singleton_class.send(:redefine_method, name, &scope_proc)
end
protected
def valid_scope_name?(name)
- if !scopes[name] && respond_to?(name, true)
+ if respond_to?(name, true)
logger.warn "Creating scope :#{name}. " \
"Overwriting existing method #{self.name}.#{name}."
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 522c0cfc9f..08b27b6a8e 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -403,12 +403,6 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
- elsif existing_records.count == 0 #Existing record but not yet associated
- existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
- if !call_reject_if(association_name, attributes)
- association.send(:add_record_to_target_with_callbacks, existing_record) if !association.loaded?
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
- end
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
unless association.loaded? || call_reject_if(association_name, attributes)
# Make sure we are operating on the actual object which is in the association's
@@ -452,6 +446,7 @@ module ActiveRecord
end
def call_reject_if(association_name, attributes)
+ return false if has_destroy_flag?(attributes)
case callback = self.nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ff36814684..6b3c38cb58 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
db_namespace = namespace :db do
task :load_config => :rails_env do
require 'active_record'
@@ -135,7 +137,7 @@ db_namespace = namespace :db do
end
def local_database?(config, &block)
- if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank?
+ if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank?
yield
else
$stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index e801bc4afa..bcba85d7a4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/deprecation'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
# = Active Record Reflection
@@ -163,7 +164,7 @@ module ActiveRecord
def initialize(macro, name, options, active_record)
super
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ @collection = macro.in?([:has_many, :has_and_belongs_to_many])
end
# Returns a new, unsaved instance of the associated class. +options+ will
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 896daf516e..359f9d8a66 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,7 +6,7 @@ module ActiveRecord
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
@@ -15,14 +15,16 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
attr_reader :table, :klass, :loaded
- attr_accessor :extensions
+ attr_accessor :extensions, :default_scoped
alias :loaded? :loaded
+ alias :default_scoped? :default_scoped
def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
@loaded = false
+ @default_scoped = false
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@@ -46,17 +48,30 @@ module ActiveRecord
im = arel.create_insert
im.into @table
+ conn = @klass.connection
+
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = conn.substitute_at(tuple.first, i)
+ end
+
if values.empty? # empty insert
im.values = im.create_values [connection.null_insert_value], []
else
- im.insert values
+ im.insert substitutes
end
- @klass.connection.insert(
+ conn.insert(
im.to_sql,
'SQL',
primary_key,
- primary_key_value)
+ primary_key_value,
+ nil,
+ binds)
end
def new(*args, &block)
@@ -154,12 +169,7 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- @klass.scoped_methods << self
- begin
- yield
- ensure
- @klass.scoped_methods.pop
- end
+ @klass.send(:with_scope, self, :overwrite) { yield }
end
# Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -194,6 +204,7 @@ module ActiveRecord
# # The same idea applies to limit and order
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
def update_all(updates, conditions = nil, options = {})
+ IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
if conditions || options.present?
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
else
@@ -322,6 +333,7 @@ module ActiveRecord
# 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)
+ IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
if conditions
where(conditions).delete_all
else
@@ -353,6 +365,7 @@ module ActiveRecord
# # Delete multiple rows
# Todo.delete([2,3,4])
def delete(id_or_array)
+ IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
where(primary_key => id_or_array).delete_all
end
@@ -374,7 +387,7 @@ module ActiveRecord
end
def where_values_hash
- equalities = @where_values.grep(Arel::Nodes::Equality).find_all { |node|
+ equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == table_name
}
@@ -402,13 +415,20 @@ module ActiveRecord
to_a.inspect
end
+ def with_default_scope #:nodoc:
+ if default_scoped?
+ default_scope = @klass.send(:build_default_scope)
+ default_scope ? default_scope.merge(self) : self
+ else
+ self
+ end
+ end
+
protected
def method_missing(method, *args, &block)
if Array.method_defined?(method)
to_a.send(method, *args, &block)
- elsif @klass.scopes[method]
- merge(@klass.send(method, *args, &block))
elsif @klass.respond_to?(method)
scoping { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8fa315bdf3..aae257a0e7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -309,9 +309,18 @@ module ActiveRecord
def find_one(id)
id = id.id if ActiveRecord::Base === id
+ if IdentityMap.enabled? && where_values.blank? &&
+ limit_value.blank? && order_values.blank? &&
+ includes_values.blank? && preload_values.blank? &&
+ readonly_value.nil? && joins_values.blank? &&
+ !@klass.locking_enabled? &&
+ record = IdentityMap.get(@klass, id)
+ return record
+ end
+
column = columns_hash[primary_key]
- substitute = connection.substitute_for(column, @bind_values)
+ substitute = connection.substitute_at(column, @bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values = [[column, id]]
record = relation.first
@@ -343,8 +352,8 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.wheres.map { |x| x.value }.join(', ')
- conditions = " [WHERE #{conditions}]" if conditions.present?
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
error = "Couldn't find all #{@klass.name.pluralize} with IDs "
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 02b7056989..94aa999715 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -8,7 +8,8 @@ module ActiveRecord
attr_accessor :includes_values, :eager_load_values, :preload_values,
:select_values, :group_values, :order_values, :joins_values,
:where_values, :having_values, :bind_values,
- :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
+ :from_value, :reorder_value
def includes(*args)
args.reject! {|a| a.blank? }
@@ -63,7 +64,11 @@ module ActiveRecord
end
def reorder(*args)
- except(:order).order(args)
+ return self if args.blank?
+
+ relation = clone
+ relation.reorder_value = args.flatten
+ relation
end
def joins(*args)
@@ -163,7 +168,7 @@ module ActiveRecord
end
def arel
- @arel ||= build_arel
+ @arel ||= with_default_scope.build_arel
end
def build_arel
@@ -180,7 +185,8 @@ module ActiveRecord
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
- arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty?
+ order = @reorder_value ? @reorder_value : @order_values
+ arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
build_select(arel, @select_values.uniq)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 128e0fbd86..69706b5ead 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -8,6 +8,8 @@ module ActiveRecord
merged_relation = clone
+ r = r.with_default_scope if r.default_scoped? && r.klass != klass
+
Relation::ASSOCIATION_METHODS.each do |method|
value = r.send(:"#{method}_values")
@@ -70,6 +72,7 @@ module ActiveRecord
#
def except(*skips)
result = self.class.new(@klass, table)
+ result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
@@ -94,6 +97,7 @@ module ActiveRecord
#
def only(*onlies)
result = self.class.new(@klass, table)
+ result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 0465b21e88..243012f88c 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -1,9 +1,9 @@
module ActiveRecord
###
- # This class encapsulates a Result returned from calling +exec+ on any
+ # This class encapsulates a Result returned from calling +exec_query+ on any
# database connection adapter. For example:
#
- # x = ActiveRecord::Base.connection.exec('SELECT * FROM foo')
+ # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
# x # => #<ActiveRecord::Result:0xdeadbeef>
class Result
include Enumerable
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 60d4c256c4..d363f36108 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -270,6 +270,7 @@ module ActiveRecord
def rolledback!(force_restore_state = false) #:nodoc:
run_callbacks :rollback
ensure
+ IdentityMap.remove(self) if IdentityMap.enabled?
restore_transaction_record_state(force_restore_state)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9cd6c26322..d1225a9ed9 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -14,6 +14,7 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
+ table = finder_class.arel_table
coder = record.class.serialized_attributes[attribute.to_s]
@@ -21,21 +22,15 @@ module ActiveRecord
value = coder.dump value
end
- sql, params = mount_sql_and_params(finder_class, record.class.quoted_table_name, attribute, value)
-
- relation = finder_class.unscoped.where(sql, *params)
+ relation = build_relation(finder_class, table, attribute, value)
+ relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
Array.wrap(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
- relation = relation.where(scope_item => scope_value)
- end
-
- if record.persisted?
- # TODO : This should be in Arel
- relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
+ relation = relation.and(table[scope_item].eq(scope_value))
end
- if relation.exists?
+ if finder_class.unscoped.where(relation).exists?
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
end
@@ -57,27 +52,18 @@ module ActiveRecord
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
- def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc:
+ def build_relation(klass, table, attribute, value) #:nodoc:
column = klass.columns_hash[attribute.to_s]
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
- operator = if value.nil?
- "IS ?"
- elsif column.text?
- value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
- "#{klass.connection.case_sensitive_equality_operator} ?"
- else
- "= ?"
- end
-
- sql_attribute = "#{table_name}.#{klass.connection.quote_column_name(attribute)}"
-
- if value.nil? || (options[:case_sensitive] || !column.text?)
- sql = "#{sql_attribute} #{operator}"
+ if !options[:case_sensitive] && column.text?
+ relation = table[attribute].matches(value)
else
- sql = "LOWER(#{sql_attribute}) = LOWER(?)"
+ value = klass.connection.case_sensitive_modifier(value)
+ relation = table[attribute].eq(value)
end
- [sql, [value]]
+ relation
end
end
diff --git a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
index afcda2a98a..90923f6e74 100644
--- a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb
@@ -1,4 +1,5 @@
require 'rails/generators/active_record'
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module Generators
@@ -13,7 +14,7 @@ module ActiveRecord
def session_table_name
current_table_name = ActiveRecord::SessionStore::Session.table_name
- if ["sessions", "session"].include?(current_table_name)
+ if current_table_name.in?(["sessions", "session"])
current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session')
end
current_table_name
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index eb3f8143e7..eee771ecff 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -44,7 +44,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_bind_value_substitute
- bind_param = @connection.substitute_for('foo', [])
+ bind_param = @connection.substitute_at('foo', 0)
assert_equal Arel.sql('?'), bind_param
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
new file mode 100644
index 0000000000..146b77a95c
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapterTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ @conn.exec_query('drop table if exists ex')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex` (
+ `id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ `number` integer,
+ `data` varchar(255))
+ eosql
+ end
+
+ def test_client_encoding
+ if "<3".respond_to?(:encoding)
+ assert_equal Encoding::UTF_8, @conn.client_encoding
+ else
+ assert_equal 'utf8', @conn.client_encoding
+ end
+ end
+
+ def test_exec_insert_number
+ insert(@conn, 'number' => 10)
+
+ result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
+
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.last.last
+ end
+
+ def test_exec_insert_string
+ str = 'いただきます!'
+ insert(@conn, 'number' => 10, 'data' => str)
+
+ result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
+
+ value = result.rows.last.last
+
+ if "<3".respond_to?(:encoding)
+ # FIXME: this should probably be inside the mysql AR adapter?
+ value.force_encoding(@conn.client_encoding)
+
+ # The strings in this file are utf-8, so transcode to utf-8
+ value.encode!(Encoding::UTF_8)
+ end
+
+ assert_equal str, value
+ end
+
+ private
+ def insert(ctx, data)
+ binds = data.map { |name, value|
+ [ctx.columns('ex').find { |x| x.name == name }, value]
+ }
+ columns = binds.map(&:first).map(&:name)
+
+ sql = "INSERT INTO ex (#{columns.join(", ")})
+ VALUES (#{(['?'] * columns.length).join(', ')})"
+
+ ctx.exec_insert(sql, 'SQL', binds)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
new file mode 100644
index 0000000000..9673e2bb46
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 1, @conn.type_cast(true, nil)
+ assert_equal 1, @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 0, @conn.type_cast(false, nil)
+ assert_equal 0, @conn.type_cast(false, c)
+ end
+ end
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index b0a4a4e39d..7c49236854 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require "cases/helper"
module ActiveRecord
@@ -6,7 +7,52 @@ module ActiveRecord
def setup
@connection = ActiveRecord::Base.connection
@connection.exec_query('drop table if exists ex')
- @connection.exec_query('create table ex(id serial primary key, data character varying(255))')
+ @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
+ end
+
+ def test_serial_sequence
+ assert_equal 'public.accounts_id_seq',
+ @connection.serial_sequence('accounts', 'id')
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.serial_sequence('zomg', 'id')
+ end
+ end
+
+ def test_default_sequence_name
+ assert_equal 'accounts_id_seq',
+ @connection.default_sequence_name('accounts', 'id')
+
+ assert_equal 'accounts_id_seq',
+ @connection.default_sequence_name('accounts')
+ end
+
+ def test_default_sequence_name_bad_table
+ assert_equal 'zomg_id_seq',
+ @connection.default_sequence_name('zomg', 'id')
+
+ assert_equal 'zomg_id_seq',
+ @connection.default_sequence_name('zomg')
+ end
+
+ def test_exec_insert_number
+ insert(@connection, 'number' => 10)
+
+ result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
+
+ assert_equal 1, result.rows.length
+ assert_equal "10", result.rows.last.last
+ end
+
+ def test_exec_insert_string
+ str = 'いただきます!'
+ insert(@connection, 'number' => 10, 'data' => str)
+
+ result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
+
+ value = result.rows.last.last
+
+ assert_equal str, value
end
def test_table_alias_length
@@ -56,13 +102,28 @@ module ActiveRecord
assert_equal [['1', 'foo']], result.rows
end
- def test_substitute_for
- bind = @connection.substitute_for(nil, [])
+ def test_substitute_at
+ bind = @connection.substitute_at(nil, 0)
assert_equal Arel.sql('$1'), bind
- bind = @connection.substitute_for(nil, [nil])
+ bind = @connection.substitute_at(nil, 1)
assert_equal Arel.sql('$2'), bind
end
+
+ private
+ def insert(ctx, data)
+ binds = data.map { |name, value|
+ [ctx.columns('ex').find { |x| x.name == name }, value]
+ }
+ columns = binds.map(&:first).map(&:name)
+
+ bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
+
+ sql = "INSERT INTO ex (#{columns.join(", ")})
+ VALUES (#{bind_subs.join(', ')})"
+
+ ctx.exec_insert(sql, 'SQL', binds)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
new file mode 100644
index 0000000000..172055f15c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 't', @conn.type_cast(true, nil)
+ assert_equal 't', @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'boolean')
+ assert_equal 'f', @conn.type_cast(false, nil)
+ assert_equal 'f', @conn.type_cast(false, c)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
deleted file mode 100644
index d1fc470907..0000000000
--- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-# encoding: utf-8
-require "cases/helper"
-require 'models/binary'
-
-module ActiveRecord
- module ConnectionAdapters
- class SQLiteAdapterTest < ActiveRecord::TestCase
- class DualEncoding < ActiveRecord::Base
- end
-
- def setup
- @ctx = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => nil
- @ctx.execute <<-eosql
- CREATE TABLE items (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- end
-
- def test_quote_binary_column_escapes_it
- DualEncoding.connection.execute(<<-eosql)
- CREATE TABLE dual_encodings (
- id integer PRIMARY KEY AUTOINCREMENT,
- name string,
- data binary
- )
- eosql
- str = "\x80".force_encoding("ASCII-8BIT")
- binary = DualEncoding.new :name => 'いただきます!', :data => str
- binary.save!
- assert_equal str, binary.data
- end
-
- def test_execute
- @ctx.execute "INSERT INTO items (number) VALUES (10)"
- records = @ctx.execute "SELECT * FROM items"
- assert_equal 1, records.length
-
- record = records.first
- assert_equal 10, record['number']
- assert_equal 1, record['id']
- end
-
- def test_quote_string
- assert_equal "''", @ctx.quote_string("'")
- end
-
- def test_insert_sql
- 2.times do |i|
- rv = @ctx.insert_sql "INSERT INTO items (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
- end
-
- records = @ctx.execute "SELECT * FROM items"
- assert_equal 2, records.length
- end
-
- def test_insert_sql_logged
- sql = "INSERT INTO items (number) VALUES (10)"
- name = "foo"
-
- assert_logged([[sql, name]]) do
- @ctx.insert_sql sql, name
- end
- end
-
- def test_insert_id_value_returned
- sql = "INSERT INTO items (number) VALUES (10)"
- idval = 'vuvuzela'
- id = @ctx.insert_sql sql, nil, nil, idval
- assert_equal idval, id
- end
-
- def test_select_rows
- 2.times do |i|
- @ctx.create "INSERT INTO items (number) VALUES (#{i})"
- end
- rows = @ctx.select_rows 'select number, id from items'
- assert_equal [[0, 1], [1, 2]], rows
- end
-
- def test_select_rows_logged
- sql = "select * from items"
- name = "foo"
-
- assert_logged([[sql, name]]) do
- @ctx.select_rows sql, name
- end
- end
-
- def test_transaction
- count_sql = 'select count(*) from items'
-
- @ctx.begin_db_transaction
- @ctx.create "INSERT INTO items (number) VALUES (10)"
-
- assert_equal 1, @ctx.select_rows(count_sql).first.first
- @ctx.rollback_db_transaction
- assert_equal 0, @ctx.select_rows(count_sql).first.first
- end
-
- def test_tables
- assert_equal %w{ items }, @ctx.tables
-
- @ctx.execute <<-eosql
- CREATE TABLE people (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- assert_equal %w{ items people }.sort, @ctx.tables.sort
- end
-
- def test_tables_logs_name
- name = "hello"
- assert_logged [[name]] do
- @ctx.tables(name)
- assert_not_nil @ctx.logged.first.shift
- end
- end
-
- def test_columns
- columns = @ctx.columns('items').sort_by { |x| x.name }
- assert_equal 2, columns.length
- assert_equal %w{ id number }.sort, columns.map { |x| x.name }
- assert_equal [nil, nil], columns.map { |x| x.default }
- assert_equal [true, true], columns.map { |x| x.null }
- end
-
- def test_columns_with_default
- @ctx.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer default 10
- )
- eosql
- column = @ctx.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert_equal 10, column.default
- end
-
- def test_columns_with_not_null
- @ctx.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- column = @ctx.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert !column.null, "column should not be null"
- end
-
- def test_indexes_logs
- intercept_logs_on @ctx
- assert_difference('@ctx.logged.length') do
- @ctx.indexes('items')
- end
- assert_match(/items/, @ctx.logged.last.first)
- end
-
- def test_no_indexes
- assert_equal [], @ctx.indexes('items')
- end
-
- def test_index
- @ctx.add_index 'items', 'id', :unique => true, :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
-
- assert_equal 'items', index.table
- assert index.unique, 'index is unique'
- assert_equal ['id'], index.columns
- end
-
- def test_non_unique_index
- @ctx.add_index 'items', 'id', :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
- assert !index.unique, 'index is not unique'
- end
-
- def test_compound_index
- @ctx.add_index 'items', %w{ id number }, :name => 'fun'
- index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
- assert_equal %w{ id number }.sort, index.columns.sort
- end
-
- def test_primary_key
- assert_equal 'id', @ctx.primary_key('items')
-
- @ctx.execute <<-eosql
- CREATE TABLE foos (
- internet integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- assert_equal 'internet', @ctx.primary_key('foos')
- end
-
- def test_no_primary_key
- @ctx.execute 'CREATE TABLE failboat (number integer not null)'
- assert_nil @ctx.primary_key('failboat')
- end
-
- private
-
- def assert_logged logs
- intercept_logs_on @ctx
- yield
- assert_equal logs, @ctx.logged
- end
-
- def intercept_logs_on ctx
- @ctx.extend(Module.new {
- attr_accessor :logged
- def log sql, name
- @logged << [sql, name]
- yield
- end
- })
- @ctx.logged = []
- end
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
new file mode 100644
index 0000000000..0d9db92447
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -0,0 +1,93 @@
+require "cases/helper"
+require 'bigdecimal'
+require 'yaml'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLiteAdapter
+ class QuotingTest < ActiveRecord::TestCase
+ def setup
+ @conn = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => 100
+ end
+
+ def test_type_cast_symbol
+ assert_equal 'foo', @conn.type_cast(:foo, nil)
+ end
+
+ def test_type_cast_date
+ date = Date.today
+ expected = @conn.quoted_date(date)
+ assert_equal expected, @conn.type_cast(date, nil)
+ end
+
+ def test_type_cast_time
+ time = Time.now
+ expected = @conn.quoted_date(time)
+ assert_equal expected, @conn.type_cast(time, nil)
+ end
+
+ def test_type_cast_numeric
+ assert_equal 10, @conn.type_cast(10, nil)
+ assert_equal 2.2, @conn.type_cast(2.2, nil)
+ end
+
+ def test_type_cast_nil
+ assert_equal nil, @conn.type_cast(nil, nil)
+ end
+
+ def test_type_cast_true
+ c = Column.new(nil, 1, 'int')
+ assert_equal 't', @conn.type_cast(true, nil)
+ assert_equal 1, @conn.type_cast(true, c)
+ end
+
+ def test_type_cast_false
+ c = Column.new(nil, 1, 'int')
+ assert_equal 'f', @conn.type_cast(false, nil)
+ assert_equal 0, @conn.type_cast(false, c)
+ end
+
+ def test_type_cast_string
+ assert_equal '10', @conn.type_cast('10', nil)
+
+ c = Column.new(nil, 1, 'int')
+ assert_equal 10, @conn.type_cast('10', c)
+
+ c = Column.new(nil, 1, 'float')
+ assert_equal 10.1, @conn.type_cast('10.1', c)
+
+ c = Column.new(nil, 1, 'binary')
+ assert_equal '10.1', @conn.type_cast('10.1', c)
+
+ c = Column.new(nil, 1, 'date')
+ assert_equal '10.1', @conn.type_cast('10.1', c)
+ end
+
+ def test_type_cast_bigdecimal
+ bd = BigDecimal.new '10.0'
+ assert_equal bd.to_s('F'), @conn.type_cast(bd, nil)
+ end
+
+ def test_type_cast_unknown
+ obj = Class.new.new
+ assert_equal YAML.dump(obj), @conn.type_cast(obj, nil)
+ end
+
+ def test_quoted_id
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+
+ def id
+ 10
+ end
+ }.new
+ assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index b8abdface4..6ff04e3eb3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,12 +1,34 @@
+# encoding: utf-8
require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class SQLite3AdapterTest < ActiveRecord::TestCase
+ class DualEncoding < ActiveRecord::Base
+ end
+
def setup
@conn = Base.sqlite3_connection :database => ':memory:',
:adapter => 'sqlite3',
:timeout => 100
+ @conn.execute <<-eosql
+ CREATE TABLE items (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ end
+
+ def test_exec_insert
+ column = @conn.columns('items').find { |col| col.name == 'number' }
+ vals = [[column, 10]]
+ @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals)
+
+ result = @conn.exec_query(
+ 'select number from items where number = ?', 'SQL', vals)
+
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.first.first
end
def test_primary_key_returns_nil_for_no_pk
@@ -58,7 +80,7 @@ module ActiveRecord
end
def test_bind_value_substitute
- bind_param = @conn.substitute_for('foo', [])
+ bind_param = @conn.substitute_at('foo', 0)
assert_equal Arel.sql('?'), bind_param
end
@@ -102,6 +124,213 @@ module ActiveRecord
assert_equal [[1, 'foo']], result.rows
end
+
+ def test_quote_binary_column_escapes_it
+ return unless "<3".respond_to?(:encode)
+
+ DualEncoding.connection.execute(<<-eosql)
+ CREATE TABLE dual_encodings (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ name string,
+ data binary
+ )
+ eosql
+ str = "\x80".force_encoding("ASCII-8BIT")
+ binary = DualEncoding.new :name => 'いただきます!', :data => str
+ binary.save!
+ assert_equal str, binary.data
+ end
+
+ def test_execute
+ @conn.execute "INSERT INTO items (number) VALUES (10)"
+ records = @conn.execute "SELECT * FROM items"
+ assert_equal 1, records.length
+
+ record = records.first
+ assert_equal 10, record['number']
+ assert_equal 1, record['id']
+ end
+
+ def test_quote_string
+ assert_equal "''", @conn.quote_string("'")
+ end
+
+ def test_insert_sql
+ 2.times do |i|
+ rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})"
+ assert_equal(i + 1, rv)
+ end
+
+ records = @conn.execute "SELECT * FROM items"
+ assert_equal 2, records.length
+ end
+
+ def test_insert_sql_logged
+ sql = "INSERT INTO items (number) VALUES (10)"
+ name = "foo"
+
+ assert_logged([[sql, name, []]]) do
+ @conn.insert_sql sql, name
+ end
+ end
+
+ def test_insert_id_value_returned
+ sql = "INSERT INTO items (number) VALUES (10)"
+ idval = 'vuvuzela'
+ id = @conn.insert_sql sql, nil, nil, idval
+ assert_equal idval, id
+ end
+
+ def test_select_rows
+ 2.times do |i|
+ @conn.create "INSERT INTO items (number) VALUES (#{i})"
+ end
+ rows = @conn.select_rows 'select number, id from items'
+ assert_equal [[0, 1], [1, 2]], rows
+ end
+
+ def test_select_rows_logged
+ sql = "select * from items"
+ name = "foo"
+
+ assert_logged([[sql, name, []]]) do
+ @conn.select_rows sql, name
+ end
+ end
+
+ def test_transaction
+ count_sql = 'select count(*) from items'
+
+ @conn.begin_db_transaction
+ @conn.create "INSERT INTO items (number) VALUES (10)"
+
+ assert_equal 1, @conn.select_rows(count_sql).first.first
+ @conn.rollback_db_transaction
+ assert_equal 0, @conn.select_rows(count_sql).first.first
+ end
+
+ def test_tables
+ assert_equal %w{ items }, @conn.tables
+
+ @conn.execute <<-eosql
+ CREATE TABLE people (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ )
+ eosql
+ assert_equal %w{ items people }.sort, @conn.tables.sort
+ end
+
+ def test_tables_logs_name
+ name = "hello"
+ assert_logged [[name, []]] do
+ @conn.tables(name)
+ assert_not_nil @conn.logged.first.shift
+ end
+ end
+
+ def test_columns
+ columns = @conn.columns('items').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
+
+ def test_columns_with_default
+ @conn.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer default 10
+ )
+ eosql
+ column = @conn.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
+
+ def test_columns_with_not_null
+ @conn.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ column = @conn.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert !column.null, "column should not be null"
+ end
+
+ def test_indexes_logs
+ intercept_logs_on @conn
+ assert_difference('@conn.logged.length') do
+ @conn.indexes('items')
+ end
+ assert_match(/items/, @conn.logged.last.first)
+ end
+
+ def test_no_indexes
+ assert_equal [], @conn.indexes('items')
+ end
+
+ def test_index
+ @conn.add_index 'items', 'id', :unique => true, :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+
+ assert_equal 'items', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
+
+ def test_non_unique_index
+ @conn.add_index 'items', 'id', :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ assert !index.unique, 'index is not unique'
+ end
+
+ def test_compound_index
+ @conn.add_index 'items', %w{ id number }, :name => 'fun'
+ index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
+
+ def test_primary_key
+ assert_equal 'id', @conn.primary_key('items')
+
+ @conn.execute <<-eosql
+ CREATE TABLE foos (
+ internet integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ assert_equal 'internet', @conn.primary_key('foos')
+ end
+
+ def test_no_primary_key
+ @conn.execute 'CREATE TABLE failboat (number integer not null)'
+ assert_nil @conn.primary_key('failboat')
+ end
+
+ private
+
+ def assert_logged logs
+ intercept_logs_on @conn
+ yield
+ assert_equal logs, @conn.logged
+ end
+
+ def intercept_logs_on ctx
+ @conn.extend(Module.new {
+ attr_accessor :logged
+ def log sql, name, binds = []
+ @logged << [sql, name, binds]
+ yield
+ end
+ })
+ @conn.logged = []
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 16d4877fe8..007f11b535 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -70,16 +70,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
car = cars(:honda)
- original_scoped_methods = Bulb.scoped_methods
+ scoped_count = car.foo_bulbs.scoped.where_values.count
- bulb = car.bulbs.build
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.build
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = car.bulbs.create
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.create
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = car.bulbs.create!
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = car.foo_bulbs.create!
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
end
def test_no_sql_should_be_fired_if_association_already_loaded
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c1dad5e246..f3c96ccbe6 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -165,16 +165,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
- original_scoped_methods = Bulb.scoped_methods.dup
+ scoped_count = pirate.association(:foo_bulb).scoped.where_values.count
- bulb = pirate.build_bulb
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.build_foo_bulb
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = pirate.create_bulb
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.create_foo_bulb
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
- bulb = pirate.create_bulb!
- assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize
+ bulb = pirate.create_foo_bulb!
+ assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
end
def test_create_association
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 5a7b139030..49a1c117bc 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/tag'
require 'models/tagging'
require 'models/post'
@@ -453,7 +454,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.tags.include?(new_tag)
assert new_tag.persisted?
- assert saved_post.reload.tags(true).include?(new_tag)
+ assert new_tag.in?(saved_post.reload.tags(true))
new_post = Post.new(:title => "Association replacmenet works!", :body => "You best believe it.")
@@ -466,7 +467,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_post.save!
assert new_post.persisted?
- assert new_post.reload.tags(true).include?(saved_tag)
+ assert saved_tag.in?(new_post.reload.tags(true))
assert !posts(:thinking).tags.build.persisted?
assert !posts(:thinking).tags.new.persisted?
@@ -515,10 +516,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
- author = authors(:david)
- author.stubs(:_read_attribute).with('comments_count').returns(100)
- assert_equal 100, author.comments.size
- assert !author.comments.loaded?
+ c = categories(:general)
+ c.categorizations_count = 100
+ assert_equal 100, c.categorizations.size
+ assert !c.categorizations.loaded?
end
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index d0a9028264..3641031d12 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
@@ -41,13 +42,13 @@ module ActiveRecord
instance = @klass.new
@klass.column_names.each do |name|
- assert ! instance.methods.map(&:to_s).include?(name)
+ assert !name.in?(instance.methods.map(&:to_s))
end
@klass.define_attribute_methods
@klass.column_names.each do |name|
- assert(instance.methods.map(&:to_s).include?(name), "#{name} is not defined")
+ assert name.in?(instance.methods.map(&:to_s)), "#{name} is not defined"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 84f75cc803..5074ae50ab 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/minimalistic'
require 'models/developer'
require 'models/auto_id'
@@ -9,6 +10,7 @@ require 'models/company'
require 'models/category'
require 'models/reply'
require 'models/contact'
+require 'models/keyboard'
class AttributeMethodsTest < ActiveRecord::TestCase
fixtures :topics, :developers, :companies, :computers
@@ -76,6 +78,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_respond_to?
topic = Topic.find(1)
assert_respond_to topic, "title"
+ assert_respond_to topic, "_title"
assert_respond_to topic, "title?"
assert_respond_to topic, "title="
assert_respond_to topic, :title
@@ -87,6 +90,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert !topic.respond_to?(:nothingness)
end
+ def test_respond_to_with_custom_primary_key
+ keyboard = Keyboard.create
+ assert_not_nil keyboard.key_number
+ assert_equal keyboard.key_number, keyboard.id
+ assert keyboard.respond_to?('key_number')
+ assert keyboard.respond_to?('_key_number')
+ assert keyboard.respond_to?('id')
+ assert keyboard.respond_to?('_id')
+ end
+
# Syck calls respond_to? before actually calling initialize
def test_respond_to_with_allocated_object
topic = Topic.allocate
@@ -638,7 +651,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def time_related_columns_on_topic
- Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
+ Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) }
end
def serialized_columns_on_topic
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index aeb0b28bab..e57c5b3b87 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -172,7 +172,7 @@ class BasicsTest < ActiveRecord::TestCase
with_active_record_default_timezone :utc do
time = Time.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
@@ -186,7 +186,7 @@ class BasicsTest < ActiveRecord::TestCase
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
@@ -199,7 +199,7 @@ class BasicsTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
time = Time.utc(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
@@ -212,7 +212,7 @@ class BasicsTest < ActiveRecord::TestCase
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).written_on
+ saved_time = Topic.find(topic.id).reload.written_on
assert_equal time, saved_time
assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
@@ -1048,7 +1048,7 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.new(:content => myobj)
assert topic.save
Topic.serialize(:content, Hash)
- assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).reload.content }
ensure
Topic.serialize(:content)
end
@@ -1626,18 +1626,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal c1, c2
end
- def test_default_scope_is_reset
+ def test_current_scope_is_reset
Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base)
- UnloadablePost.table_name = 'posts'
- UnloadablePost.class_eval do
- default_scope order('posts.comments_count ASC')
- end
- UnloadablePost.scoped_methods # make Thread.current[:UnloadablePost_scoped_methods] not nil
+ UnloadablePost.send(:current_scope=, UnloadablePost.scoped)
UnloadablePost.unloadable
- assert_not_nil Thread.current[:UnloadablePost_scoped_methods]
+ assert_not_nil Thread.current[:UnloadablePost_current_scope]
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil Thread.current[:UnloadablePost_scoped_methods]
+ assert_nil Thread.current[:UnloadablePost_current_scope]
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 8545ba97cc..06c14cb108 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require "cases/helper"
# Without using prepared statements, it makes no sense to test
@@ -9,6 +10,24 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
class BinaryTest < ActiveRecord::TestCase
FIXTURES = %w(flowers.jpg example.log)
+ def test_mixed_encoding
+ str = "\x80"
+ str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding)
+
+ binary = Binary.new :name => 'いただきます!', :data => str
+ binary.save!
+ binary.reload
+ assert_equal str, binary.data
+
+ name = binary.name
+
+ # Mysql adapter doesn't properly encode things, so we have to do it
+ if current_adapter?(:MysqlAdapter)
+ name.force_encoding('UTF-8') if name.respond_to?(:force_encoding)
+ end
+ assert_equal 'いただきます!', name
+ end
+
def test_load_save
Binary.delete_all
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 19383bb06b..3652255c38 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -33,7 +33,7 @@ module ActiveRecord
# FIXME: use skip with minitest
return unless @connection.supports_statement_cache?
- sub = @connection.substitute_for(@pk, [])
+ sub = @connection.substitute_at(@pk, 0)
binds = [[@pk, 1]]
sql = "select * from topics where id = #{sub}"
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index deaf5252db..b3a281d960 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/core_ext/object/inclusion'
require 'models/default'
require 'models/entrant'
@@ -94,7 +95,7 @@ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_equal 0, klass.columns_hash['zero'].default
assert !klass.columns_hash['zero'].null
# 0 in MySQL 4, nil in 5.
- assert [0, nil].include?(klass.columns_hash['omit'].default)
+ assert klass.columns_hash['omit'].default.in?([0, nil])
assert !klass.columns_hash['omit'].null
assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 3c242667eb..655437318f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -247,9 +247,10 @@ class FinderTest < ActiveRecord::TestCase
def test_find_only_some_columns
topic = Topic.find(1, :select => "author_name")
assert_raise(ActiveModel::MissingAttributeError) {topic.title}
+ assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
assert !topic.attribute_present?("title")
- #assert !topic.respond_to?("title")
+ assert !topic.attribute_present?(:title)
assert topic.attribute_present?("author_name")
assert_respond_to topic, "author_name"
end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 89f7b92d09..199e59657d 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -90,6 +90,13 @@ class IdentityMapTest < ActiveRecord::TestCase
)
end
+ def test_queries_are_not_executed_when_finding_by_id
+ Post.find 1
+ assert_no_queries do
+ Post.find 1
+ end
+ end
+
##############################################################################
# Tests checking if IM is functioning properly on more advanced finds #
# and associations #
@@ -144,7 +151,7 @@ class IdentityMapTest < ActiveRecord::TestCase
s = Subscriber.find('swistak')
- assert_equal({'name' => ["Raczkowski Marcin", "Swistak Sreberkowiec"]}, swistak.changes)
+ assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
assert_equal("Swistak Sreberkowiec", swistak.name)
end
@@ -159,8 +166,8 @@ class IdentityMapTest < ActiveRecord::TestCase
s = Subscriber.find('swistak')
assert_equal("Swistak Sreberkowiec", swistak.name)
- assert_equal({}, swistak.changes)
- assert !swistak.name_changed?
+ assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
+ assert swistak.name_changed?
end
def test_has_many_associations
@@ -381,6 +388,15 @@ class IdentityMapTest < ActiveRecord::TestCase
assert_not_nil post.title
end
+ def test_log
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::DEBUG
+ Post.find 1
+ Post.find 1
+ assert_match(/Post with ID = 1 loaded from Identity Map/, log.string)
+ end
+
# Currently AR is not allowing changing primary key (see Persistence#update)
# So we ignore it. If this changes, this test needs to be uncommented.
# def test_updating_of_pkey
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 8ebde933b4..5f55299065 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -8,6 +8,8 @@ class LogSubscriberTest < ActiveRecord::TestCase
def setup
@old_logger = ActiveRecord::Base.logger
+ @using_identity_map = ActiveRecord::IdentityMap.enabled?
+ ActiveRecord::IdentityMap.enabled = false
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
@@ -16,6 +18,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
super
ActiveRecord::LogSubscriber.log_subscribers.pop
ActiveRecord::Base.logger = @old_logger
+ ActiveRecord::IdentityMap.enabled = @using_identity_map
end
def set_logger(logger)
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 7e8383da9e..7f0f007a70 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -249,22 +249,21 @@ class MethodScopingTest < ActiveRecord::TestCase
end
def test_scoped_with_duck_typing
- scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
+ scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
Developer.send(:with_scope, scoping) do
assert_equal %w(David), Developer.find(:all).map { |d| d.name }
end
end
def test_ensure_that_method_scoping_is_correctly_restored
- scoped_methods = Developer.instance_eval('current_scoped_methods')
-
begin
Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
raise "an exception"
end
rescue
end
- assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+
+ assert !Developer.scoped.where_values.include?("name = 'Jamis'")
end
end
@@ -509,14 +508,15 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_ensure_that_method_scoping_is_correctly_restored
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- scoped_methods = Developer.instance_eval('current_scoped_methods')
begin
Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
raise "an exception"
end
rescue
end
- assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+
+ assert Developer.scoped.where_values.include?("name = 'David'")
+ assert !Developer.scoped.where_values.include?("name = 'Maiha'")
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 9b20ea08de..2880fdc651 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -58,17 +58,6 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.approved.respond_to?(:tables_in_string, true)
end
- def test_subclasses_inherit_scopes
- assert Topic.scopes.include?(:base)
-
- assert Reply.scopes.include?(:base)
- assert_equal Reply.find(:all), Reply.base
- end
-
- def test_classes_dont_inherit_subclasses_scopes
- assert !ActiveRecord::Base.scopes.include?(:base)
- end
-
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
assert !Topic.find(:all, :conditions => {:approved => true}).empty?
@@ -440,26 +429,31 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
+ # Note: these next two are kinda odd because they are essentially just testing that the
+ # query cache works as it should, but they are here for legacy reasons as they was previously
+ # a separate cache on association proxies, and these show that that is not necessary.
def test_scopes_are_cached_on_associations
post = posts(:welcome)
- assert_equal post.comments.containing_the_letter_e.object_id, post.comments.containing_the_letter_e.object_id
-
- post.comments.containing_the_letter_e.all # force load
- assert_no_queries { post.comments.containing_the_letter_e.all }
+ Post.cache do
+ assert_queries(1) { post.comments.containing_the_letter_e.all }
+ assert_no_queries { post.comments.containing_the_letter_e.all }
+ end
end
def test_scopes_with_arguments_are_cached_on_associations
post = posts(:welcome)
- one = post.comments.limit_by(1).all
- assert_equal 1, one.size
+ Post.cache do
+ one = assert_queries(1) { post.comments.limit_by(1).all }
+ assert_equal 1, one.size
- two = post.comments.limit_by(2).all
- assert_equal 2, two.size
+ two = assert_queries(1) { post.comments.limit_by(2).all }
+ assert_equal 2, two.size
- assert_no_queries { post.comments.limit_by(1).all }
- assert_no_queries { post.comments.limit_by(2).all }
+ assert_no_queries { post.comments.limit_by(1).all }
+ assert_no_queries { post.comments.limit_by(2).all }
+ end
end
def test_scopes_are_reset_on_association_reload
@@ -477,6 +471,12 @@ class NamedScopeTest < ActiveRecord::TestCase
require "models/without_table"
end
end
+
+ def test_scopes_with_callables_are_deprecated
+ assert_deprecated do
+ Post.scope :WE_SO_EXCITED, lambda { |partyingpartyingpartying, yeah| fun!.fun!.fun! }
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index c57ab7ed28..6568eb1d18 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -131,6 +131,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal 'photography', interest.reload.topic
end
+ def test_destroy_works_independent_of_reject_if
+ Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true
+ man = Man.create(:name => "Jon")
+ interest = man.interests.create(:topic => 'the ladies')
+ man.update_attributes({:interests_attributes => { :_destroy => "1", :id => interest.id } })
+ assert man.reload.interests.empty?
+ end
+
def test_has_many_association_updating_a_single_record
Man.accepts_nested_attributes_for(:interests)
man = Man.create(:name => 'John')
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 53aefc7b58..287f7e255b 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -13,7 +13,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_find_queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
end
def test_find_queries_with_cache
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index b87fb51d97..80ee74e41e 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -154,15 +154,16 @@ module ActiveRecord
end
def test_crazy_object
- crazy = Class.new { def to_yaml; 'lol' end }.new
- assert_equal "'lol'", @quoter.quote(crazy, nil)
- assert_equal "'lol'", @quoter.quote(crazy, Object.new)
+ crazy = Class.new.new
+ expected = "'#{YAML.dump(crazy)}'"
+ assert_equal expected, @quoter.quote(crazy, nil)
+ assert_equal expected, @quoter.quote(crazy, Object.new)
end
def test_crazy_object_calls_quote_string
- crazy = Class.new { def to_yaml; 'lo\l' end }.new
- assert_equal "'lo\\\\l'", @quoter.quote(crazy, nil)
- assert_equal "'lo\\\\l'", @quoter.quote(crazy, Object.new)
+ crazy = Class.new { def initialize; @lol = 'lo\l' end }.new
+ assert_match "lo\\\\l", @quoter.quote(crazy, nil)
+ assert_match "lo\\\\l", @quoter.quote(crazy, Object.new)
end
def test_quote_string_no_column
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 30a783d5a2..5079aec9ba 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -132,8 +132,6 @@ class RelationScopingTest < ActiveRecord::TestCase
end
def test_ensure_that_method_scoping_is_correctly_restored
- scoped_methods = Developer.send(:current_scoped_methods)
-
begin
Developer.where("name = 'Jamis'").scoping do
raise "an exception"
@@ -141,7 +139,7 @@ class RelationScopingTest < ActiveRecord::TestCase
rescue
end
- assert_equal scoped_methods, Developer.send(:current_scoped_methods)
+ assert !Developer.scoped.where_values.include?("name = 'Jamis'")
end
end
@@ -310,72 +308,178 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_default_scope_with_lambda
- expected = Post.find_all_by_author_id(2)
- PostForAuthor.selected_author = 2
- received = PostForAuthor.all
- assert_equal expected, received
- expected = Post.find_all_by_author_id(1)
- PostForAuthor.selected_author = 1
- received = PostForAuthor.all
+ def test_default_scope_is_unscoped_on_find
+ assert_equal 1, DeveloperCalledDavid.count
+ assert_equal 11, DeveloperCalledDavid.unscoped.count
+ end
+
+ def test_default_scope_is_unscoped_on_create
+ assert_nil DeveloperCalledJamis.unscoped.create!.name
+ end
+
+ def test_default_scope_with_conditions_string
+ assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
+ assert_equal nil, DeveloperCalledDavid.create!.name
+ end
+
+ def test_default_scope_with_conditions_hash
+ assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
+ assert_equal 'Jamis', DeveloperCalledJamis.create!.name
+ end
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new { assert DeveloperOrderedBySalary.scoped.to_sql.include?('salary DESC') }.join
+ end
+ end
+
+ def test_default_scope_with_inheritance
+ wheres = InheritedPoorDeveloperCalledJamis.scoped.where_values_hash
+ assert_equal "Jamis", wheres[:name]
+ assert_equal 50000, wheres[:salary]
+ end
+
+ def test_method_scope
+ expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
assert_equal expected, received
end
- def test_default_scope_with_thing_that_responds_to_call
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'posts'
+ def test_nested_scope
+ expected = Developer.find(:all, :order => 'salary DESC, name 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
- klass.class_eval do
- default_scope Class.new(Struct.new(:klass)) {
- def call
- klass.where(:author_id => 2)
- end
- }.new(self)
+ def test_scope_overwrites_default
+ expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_reorder_overrides_default_scope_order
+ expected = Developer.order('name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_nested_exclusive_scope
+ expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
+ DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
+ assert_equal expected, received
+ end
+
+ def test_order_in_default_scope_should_prevail
+ expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_create_attribute_overwrites_default_scoping
+ assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
+ assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
+ end
+
+ def test_create_attribute_overwrites_default_values
+ assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
+ assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
+ end
+
+ def test_default_scope_attribute
+ jamis = PoorDeveloperCalledJamis.new(:name => 'David')
+ assert_equal 50000, jamis.salary
+ end
- records = klass.all
- assert_equal 3, records.length
- assert_equal 2, records.first.author_id
+ def test_where_attribute
+ aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron')
+ assert_equal 20, aaron.salary
+ assert_equal 'Aaron', aaron.name
+ end
+
+ def test_where_attribute_merge
+ aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron')
+ assert_equal 'Aaron', aaron.name
+ end
+
+ def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit
+ posts_limit_offset = Post.limit(3).offset(2)
+ posts_offset_limit = Post.offset(2).limit(3)
+ assert_equal posts_limit_offset, posts_offset_limit
+ end
+
+ def test_create_with_merge
+ aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge(
+ PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
+ assert_equal 20, aaron.salary
+ assert_equal 'Aaron', aaron.name
+
+ aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
+ create_with(:name => 'Aaron').new
+ assert_equal 20, aaron.salary
+ assert_equal 'Aaron', aaron.name
+ end
+
+ def test_create_with_reset
+ jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
+ assert_equal 'Jamis', jamis.name
+ end
+
+ def test_unscoped_with_named_scope_should_not_have_default_scope
+ assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
+
+ assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+ assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
+ end
+end
+
+class DeprecatedDefaultScopingTest < ActiveRecord::TestCase
+ fixtures :developers, :posts
+
+ def test_default_scope
+ expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
+ received = DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ assert_equal expected, received
end
def test_default_scope_is_unscoped_on_find
- assert_equal 1, DeveloperCalledDavid.count
- assert_equal 11, DeveloperCalledDavid.unscoped.count
+ assert_equal 1, DeprecatedDeveloperCalledDavid.count
+ assert_equal 11, DeprecatedDeveloperCalledDavid.unscoped.count
end
def test_default_scope_is_unscoped_on_create
- assert_nil DeveloperCalledJamis.unscoped.create!.name
+ assert_nil DeprecatedDeveloperCalledJamis.unscoped.create!.name
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
- assert_equal nil, DeveloperCalledDavid.create!.name
+ assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeprecatedDeveloperCalledDavid.find(:all).map(&:id).sort
+ assert_equal nil, DeprecatedDeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
- assert_equal 'Jamis', DeveloperCalledJamis.create!.name
+ assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeprecatedDeveloperCalledJamis.find(:all).map(&:id).sort
+ assert_equal 'Jamis', DeprecatedDeveloperCalledJamis.create!.name
end
def test_default_scoping_with_threads
2.times do
- Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
+ Thread.new { assert DeprecatedDeveloperOrderedBySalary.scoped.to_sql.include?('salary DESC') }.join
end
end
def test_default_scoping_with_inheritance
# Inherit a class having a default scope and define a new default scope
- klass = Class.new(DeveloperOrderedBySalary)
- klass.send :default_scope, :limit => 1
+ klass = Class.new(DeprecatedDeveloperOrderedBySalary)
+ ActiveSupport::Deprecation.silence { klass.send :default_scope, :limit => 1 }
# Scopes added on children should append to parent scope
- assert_equal 1, klass.scoped.limit_value
- assert_equal ['salary DESC'], klass.scoped.order_values
+ assert_equal [developers(:jamis).id], klass.all.map(&:id)
# Parent should still have the original scope
- assert_nil DeveloperOrderedBySalary.scoped.limit_value
- assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
+ assert_equal Developer.order('salary DESC').map(&:id), DeprecatedDeveloperOrderedBySalary.all.map(&:id)
end
def test_default_scope_called_twice_merges_conditions
@@ -385,8 +489,10 @@ class DefaultScopingTest < ActiveRecord::TestCase
Developer.create!(:name => "Brian", :salary => 100000)
klass = Class.new(Developer)
- klass.__send__ :default_scope, :conditions => { :name => "David" }
- klass.__send__ :default_scope, :conditions => { :salary => 100000 }
+ ActiveSupport::Deprecation.silence do
+ klass.__send__ :default_scope, :conditions => { :name => "David" }
+ klass.__send__ :default_scope, :conditions => { :salary => 100000 }
+ end
assert_equal 1, klass.count
assert_equal "David", klass.first.name
assert_equal 100000, klass.first.salary
@@ -399,9 +505,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
Developer.create!(:name => "Brian", :salary => 100000)
klass = Class.new(Developer)
- klass.class_eval do
- default_scope where("name = 'David'")
- default_scope where("salary = 100000")
+ ActiveSupport::Deprecation.silence do
+ klass.class_eval do
+ default_scope where("name = 'David'")
+ default_scope where("salary = 100000")
+ end
end
assert_equal 1, klass.count
@@ -411,96 +519,90 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_method_scope
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
+ received = DeprecatedDeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
assert_equal expected, received
end
def test_nested_scope
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ received = DeprecatedDeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
+ DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
assert_equal expected, received
end
def test_scope_overwrites_default
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
+ received = DeprecatedDeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
end
def test_reorder_overrides_default_scope_order
expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
+ received = DeprecatedDeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
assert_equal expected, received
end
def test_nested_exclusive_scope
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ received = DeprecatedDeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
+ DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
assert_equal expected, received
end
def test_order_in_default_scope_should_prevail
expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ received = DeprecatedDeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
assert_equal expected, received
end
def test_default_scope_using_relation
- posts = PostWithComment.scoped
- assert_equal 2, posts.count
+ posts = DeprecatedPostWithComment.scoped
+ assert_equal 2, posts.to_a.length
assert_equal posts(:thinking), posts.first
end
def test_create_attribute_overwrites_default_scoping
- assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
- assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
+ assert_equal 'David', DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').name
+ assert_equal 200000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
end
def test_create_attribute_overwrites_default_values
- assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
- assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
+ assert_equal nil, DeprecatedPoorDeveloperCalledJamis.create!(:salary => nil).salary
+ assert_equal 50000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').salary
end
def test_default_scope_attribute
- jamis = PoorDeveloperCalledJamis.new(:name => 'David')
+ jamis = DeprecatedPoorDeveloperCalledJamis.new(:name => 'David')
assert_equal 50000, jamis.salary
end
def test_where_attribute
- aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron')
+ aaron = DeprecatedPoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron')
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
end
def test_where_attribute_merge
- aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron')
+ aaron = DeprecatedPoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron')
assert_equal 'Aaron', aaron.name
end
- def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit
- posts_limit_offset = Post.limit(3).offset(2)
- posts_offset_limit = Post.offset(2).limit(3)
- assert_equal posts_limit_offset, posts_offset_limit
- end
-
def test_create_with_merge
- aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge(
- PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
+ aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge(
+ DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
- aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
+ aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
create_with(:name => 'Aaron').new
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
end
def test_create_with_reset
- jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
+ jamis = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
assert_equal 'Jamis', jamis.name
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 7bdbd773b6..6874bd18f8 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from].map(&:to_s).sort,
+ assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder].map(&:to_s).sort,
Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 7178bb0d00..89ee5416bf 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,14 +1,15 @@
class Bulb < ActiveRecord::Base
-
- default_scope :conditions => {:name => 'defaulty' }
+ def self.default_scope
+ where :name => 'defaulty'
+ end
belongs_to :car
- attr_reader :scoped_methods_after_initialize
+ attr_reader :scope_after_initialize
- after_initialize :record_scoped_methods_after_initialize
- def record_scoped_methods_after_initialize
- @scoped_methods_after_initialize = self.class.scoped_methods.dup
+ after_initialize :record_scope_after_initialize
+ def record_scope_after_initialize
+ @scope_after_initialize = self.class.scoped
end
end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index e7db3d3423..a978debb58 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -1,6 +1,7 @@
class Car < ActiveRecord::Base
has_many :bulbs
+ has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' }
has_many :tyres
has_many :engines
has_many :wheels, :as => :wheelable
@@ -14,9 +15,13 @@ class Car < ActiveRecord::Base
end
class CoolCar < Car
- default_scope :order => 'name desc'
+ def self.default_scope
+ order 'name desc'
+ end
end
class FastCar < Car
- default_scope order('name desc')
+ def self.default_scope
+ order 'name desc'
+ end
end
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index 09489b8ea4..39441e8610 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -13,7 +13,9 @@ end
class SpecialCategorization < ActiveRecord::Base
self.table_name = 'categorizations'
- default_scope where(:special => true)
+ def self.default_scope
+ where(:special => true)
+ end
belongs_to :author
belongs_to :category
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 2a4c37089a..3bd7db7834 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,5 +1,8 @@
class Comment < ActiveRecord::Base
- scope :limit_by, lambda {|l| limit(l) }
+ def self.limit_by(l)
+ limit(l)
+ end
+
scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'"
scope :not_again, where("comments.body NOT LIKE '%again%'")
scope :for_first_post, :conditions => { :post_id => 1 }
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 32d060cf09..10385ba899 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -86,7 +86,11 @@ end
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :order => 'salary DESC'
+
+ def self.default_scope
+ order('salary DESC')
+ end
+
scope :by_name, order('name DESC')
def self.all_ordered_by_name
@@ -98,15 +102,74 @@ end
class DeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => "name = 'David'"
+
+ def self.default_scope
+ where "name = 'David'"
+ end
end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => { :name => 'Jamis' }
+
+ def self.default_scope
+ where :name => 'Jamis'
+ end
+
+ scope :poor, where('salary < 150000')
+end
+
+class AbstractDeveloperCalledJamis < ActiveRecord::Base
+ self.abstract_class = true
+
+ def self.default_scope
+ where :name => 'Jamis'
+ end
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
+
+ def self.default_scope
+ where :name => 'Jamis', :salary => 50000
+ end
+end
+
+class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
+ self.table_name = 'developers'
+
+ def self.default_scope
+ super.where :salary => 50000
+ end
+end
+
+ActiveSupport::Deprecation.silence do
+ class DeprecatedDeveloperOrderedBySalary < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope :order => 'salary DESC'
+
+ def self.by_name
+ order('name DESC')
+ end
+
+ def self.all_ordered_by_name
+ with_scope(:find => { :order => 'name DESC' }) do
+ find(:all)
+ end
+ end
+ end
+
+ class DeprecatedDeveloperCalledDavid < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope :conditions => "name = 'David'"
+ end
+
+ class DeprecatedDeveloperCalledJamis < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope :conditions => { :name => 'Jamis' }
+ end
+
+ class DeprecatedPoorDeveloperCalledJamis < ActiveRecord::Base
+ self.table_name = 'developers'
+ default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
+ end
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 0d3f62bb33..5e0f5323e6 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -34,7 +34,7 @@ class Pirate < ActiveRecord::Base
:after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"}
has_many :birds_with_reject_all_blank, :class_name => "Bird"
- has_one :bulb, :foreign_key => :car_id
+ has_one :foo_bulb, :foreign_key => :car_id, :class_name => "Bulb", :conditions => { :name => 'foo' }
accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 82894a3d57..a91c10276b 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -7,12 +7,14 @@ class Post < ActiveRecord::Base
scope :containing_the_letter_a, where("body LIKE '%a%'")
scope :ranked_by_comments, order("comments_count DESC")
- scope :limit_by, lambda {|l| limit(l) }
- scope :with_authors_at_address, lambda { |address| {
- :conditions => [ 'authors.author_address_id = ?', address.id ],
- :joins => 'JOIN authors ON authors.id = posts.author_id'
- }
- }
+
+ def self.limit_by(l)
+ limit(l)
+ end
+
+ def self.with_authors_at_address(address)
+ where('authors.author_address_id = ?', address.id).joins('JOIN authors ON authors.id = posts.author_id')
+ end
belongs_to :author do
def greeting
@@ -27,9 +29,10 @@ class Post < ActiveRecord::Base
scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} }
scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'})
- scope :with_post, lambda {|post_id|
- { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } }
- }
+
+ def self.with_post(post_id)
+ joins(:comments).where(:comments => { :post_id => post_id })
+ end
has_many :comments do
def find_most_recent
@@ -142,20 +145,25 @@ class SubStiPost < StiPost
self.table_name = Post.table_name
end
-class PostWithComment < ActiveRecord::Base
- self.table_name = 'posts'
- default_scope where("posts.comments_count > 0").order("posts.comments_count ASC")
+ActiveSupport::Deprecation.silence do
+ class DeprecatedPostWithComment < ActiveRecord::Base
+ self.table_name = 'posts'
+ default_scope where("posts.comments_count > 0").order("posts.comments_count ASC")
+ end
end
class PostForAuthor < ActiveRecord::Base
self.table_name = 'posts'
cattr_accessor :selected_author
- default_scope lambda { where(:author_id => PostForAuthor.selected_author) }
end
class FirstPost < ActiveRecord::Base
self.table_name = 'posts'
- default_scope where(:id => 1)
+
+ def self.default_scope
+ where(:id => 1)
+ end
+
has_many :comments, :foreign_key => :post_id
has_one :comment, :foreign_key => :post_id
end
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index e33a0f2acc..76c0a1a32e 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -18,6 +18,9 @@ class Reference < ActiveRecord::Base
end
class BadReference < ActiveRecord::Base
- self.table_name ='references'
- default_scope :conditions => {:favourite => false }
+ self.table_name = 'references'
+
+ def self.default_scope
+ where :favourite => false
+ end
end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 6440dbe8ab..60e750e6c4 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -1,10 +1,20 @@
class Topic < ActiveRecord::Base
scope :base
- scope :written_before, lambda { |time|
- if time
- { :conditions => ['written_on < ?', time] }
- end
- }
+
+ ActiveSupport::Deprecation.silence do
+ scope :written_before, lambda { |time|
+ if time
+ { :conditions => ['written_on < ?', time] }
+ end
+ }
+
+ scope :with_object, Class.new(Struct.new(:klass)) {
+ def call
+ klass.where(:approved => true)
+ end
+ }.new(self)
+ end
+
scope :approved, :conditions => {:approved => true}
scope :rejected, :conditions => {:approved => false}
@@ -19,12 +29,6 @@ class Topic < ActiveRecord::Base
end
end
- scope :with_object, Class.new(Struct.new(:klass)) {
- def call
- klass.where(:approved => true)
- end
- }.new(self)
-
module NamedExtension
def two
2
diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb
index 87f80911e1..1a63d6ceb6 100644
--- a/activerecord/test/models/without_table.rb
+++ b/activerecord/test/models/without_table.rb
@@ -1,3 +1,5 @@
class WithoutTable < ActiveRecord::Base
- default_scope where(:published => true)
-end \ No newline at end of file
+ def self.default_scope
+ where(:published => true)
+ end
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 362475de36..ceadb05644 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -67,6 +67,7 @@ ActiveRecord::Schema.define do
end
create_table :binaries, :force => true do |t|
+ t.string :name
t.binary :data
end
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index 480f2fbecb..765575d866 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/uri'
+require 'active_support/core_ext/object/inclusion'
require 'net/https'
require 'date'
require 'time'
@@ -277,7 +278,7 @@ module ActiveResource
def legitimize_auth_type(auth_type)
return :basic if auth_type.nil?
auth_type = auth_type.to_sym
- [:basic, :digest].include?(auth_type) ? auth_type : :basic
+ auth_type.in?([:basic, :digest]) ? auth_type : :basic
end
end
end
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 75649053d0..e085a05f6d 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/object/inclusion'
module ActiveResource
class InvalidRequestError < StandardError; end #:nodoc:
@@ -299,7 +300,7 @@ module ActiveResource
end
def success?
- (200..299).include?(code)
+ code.in?(200..299)
end
def [](key)
diff --git a/activeresource/test/cases/http_mock_test.rb b/activeresource/test/cases/http_mock_test.rb
index 43cf5f5ef0..cd5155924a 100644
--- a/activeresource/test/cases/http_mock_test.rb
+++ b/activeresource/test/cases/http_mock_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
class HttpMockTest < ActiveSupport::TestCase
setup do
@@ -192,7 +193,7 @@ class HttpMockTest < ActiveSupport::TestCase
end
def request(method, path, headers = {}, body = nil)
- if [:put, :post].include? method
+ if method.in?([:put, :post])
@http.send(method, path, body, headers)
else
@http.send(method, path, headers)
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 373236ce9a..163f1c932c 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* Add Object#in? to test if an object is included in another object [Prem Sichanugrist, Brian Morearty, John Reitano]
+
* LocalCache strategy is now a real middleware class, not an anonymous class
posing for pictures.
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 18182bbb40..9936b33e22 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/object/inclusion'
require 'rack/utils'
module ActiveSupport
@@ -20,7 +21,7 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| ['.', '..'].include?(f)}
+ root_dirs = Dir.entries(cache_path).reject{|f| f.in?(['.', '..'])}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
@@ -161,7 +162,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if dir == cache_path
- if Dir.entries(dir).reject{|f| ['.', '..'].include?(f)}.empty?
+ if Dir.entries(dir).reject{|f| f.in?(['.', '..'])}.empty?
File.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 418102352f..656cba625c 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/inclusion'
module ActiveSupport
# \Callbacks are code hooks that are run at key points in an object's lifecycle.
@@ -412,7 +413,7 @@ module ActiveSupport
# CallbackChain.
#
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
+ type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
index 82a4f7ac5a..6fa55a9255 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -16,6 +16,6 @@ class DateTime
def in_time_zone(zone = ::Time.zone)
return self unless zone
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 3005fef44c..61a1d88b0e 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -108,7 +108,7 @@ class Hash
raise "can't typecast #{entries.inspect}"
end
end
- elsif value.has_key?("__content__")
+ elsif value['type'] == 'file' || value["__content__"].present?
content = value["__content__"]
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 790a26f5c1..9ad1e12699 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
new file mode 100644
index 0000000000..51cfc62f2b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -0,0 +1,15 @@
+class Object
+ # Returns true if this object is included in the argument. Argument must be
+ # any object which respond to +#include?+. Usage:
+ #
+ # characters = ["Konata", "Kagami", "Tsukasa"]
+ # "Konata".in?(characters) # => true
+ #
+ # This will throw an ArgumentError if the supplied argument doesnt not respond
+ # to +#include?+.
+ def in?(another_object)
+ another_object.include?(self)
+ rescue NoMethodError
+ raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index addd4dab95..c27cbc37c5 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -9,7 +9,7 @@ class ERB
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
#
- # In your ERb templates, use this method to escape any unsafe content. For example:
+ # In your ERB templates, use this method to escape any unsafe content. For example:
# <%=h @person.name %>
#
# ==== Example:
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index ff90d7ca58..0c5962858e 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -34,29 +34,36 @@ class Time
# end
# end
def zone=(time_zone)
- Thread.current[:time_zone] = get_zone(time_zone)
+ Thread.current[:time_zone] = find_zone!(time_zone)
end
# Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
def use_zone(time_zone)
- old_zone, ::Time.zone = ::Time.zone, get_zone(time_zone)
- yield
- ensure
- ::Time.zone = old_zone
+ new_zone = find_zone!(time_zone)
+ begin
+ old_zone, ::Time.zone = ::Time.zone, new_zone
+ yield
+ ensure
+ ::Time.zone = old_zone
+ end
end
- private
- def get_zone(time_zone)
- return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
- unless time_zone.respond_to?(:period_for_local)
- time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) rescue nil
- end
- # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
- if time_zone
- time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
- end
+ # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
+ def find_zone!(time_zone)
+ return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone)
+ # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ unless time_zone.respond_to?(:period_for_local)
+ time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
+ # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
+ time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
+ rescue TZInfo::InvalidTimezoneIdentifier
+ raise ArgumentError, "Invalid Timezone: #{time_zone}"
+ end
+
+ def find_zone(time_zone)
+ find_zone!(time_zone) rescue nil
+ end
end
# Returns the simultaneous time in <tt>Time.zone</tt>.
@@ -74,6 +81,6 @@ class Time
def in_time_zone(zone = ::Time.zone)
return self unless zone
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone))
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index c2deba3b1b..04df2ea562 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -46,7 +46,7 @@ module ActiveSupport
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
require 'active_support/core_ext/time/zones'
- zone_default = Time.__send__(:get_zone, app.config.time_zone)
+ zone_default = Time.find_zone!(app.config.time_zone)
unless zone_default
raise \
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c66aa78ce8..3d092529d6 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,5 +1,6 @@
require "active_support/values/time_zone"
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/object/inclusion'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
@@ -309,7 +310,7 @@ module ActiveSupport
end
def marshal_load(variables)
- initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc)
+ initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
@@ -344,7 +345,7 @@ module ActiveSupport
end
def duration_of_variable_length?(obj)
- ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include? p[0] }
+ ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
end
end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index a0479d45ac..012b956d7f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -889,6 +889,15 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal 'application/octet-stream', file.content_type
end
+ def test_tag_with_attrs_and_whitespace
+ xml = <<-XML
+ <blog name="bacon is the best">
+ </blog>
+ XML
+ hash = Hash.from_xml(xml)
+ assert_equal "bacon is the best", hash['blog']['name']
+ end
+
def test_xsd_like_types_from_xml
bacon_xml = <<-EOT
<bacon>
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
new file mode 100644
index 0000000000..1de857d678
--- /dev/null
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -0,0 +1,50 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object/inclusion'
+
+class InTest < Test::Unit::TestCase
+ def test_in_array
+ assert 1.in?([1,2])
+ assert !3.in?([1,2])
+ end
+
+ def test_in_hash
+ h = { "a" => 100, "b" => 200 }
+ assert "a".in?(h)
+ assert !"z".in?(h)
+ end
+
+ def test_in_string
+ assert "lo".in?("hello")
+ assert !"ol".in?("hello")
+ assert ?h.in?("hello")
+ end
+
+ def test_in_range
+ assert 25.in?(1..50)
+ assert !75.in?(1..50)
+ end
+
+ def test_in_set
+ s = Set.new([1,2])
+ assert 1.in?(s)
+ assert !3.in?(s)
+ end
+
+ module A
+ end
+ class B
+ include A
+ end
+ class C < B
+ end
+
+ def test_in_module
+ assert A.in?(B)
+ assert A.in?(C)
+ assert !A.in?(A)
+ end
+
+ def test_no_method_catching
+ assert_raise(ArgumentError) { 1.in?(1) }
+ end
+end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index bafa335a09..72b55183ba 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -36,6 +36,12 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id
end
+ def test_in_time_zone_with_bad_argument
+ assert_raise(ArgumentError) { @twz.in_time_zone('No such timezone exists') }
+ assert_raise(ArgumentError) { @twz.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) }
+ end
+
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
end
@@ -760,6 +766,15 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
end
end
+ def test_in_time_zone_with_invalid_argument
+ assert_raise(ArgumentError) { @t.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @dt.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @t.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @dt.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @t.in_time_zone(Object.new) }
+ assert_raise(ArgumentError) { @dt.in_time_zone(Object.new) }
+ end
+
def test_in_time_zone_with_time_local_instance
with_env_tz 'US/Eastern' do
time = Time.local(1999, 12, 31, 19) # == Time.utc(2000)
@@ -790,6 +805,14 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
end
+ def test_use_zone_raises_on_invalid_timezone
+ Time.zone = 'Alaska'
+ assert_raise ArgumentError do
+ Time.use_zone("No such timezone exists") { }
+ end
+ assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
+ end
+
def test_time_zone_getter_and_setter
Time.zone = ActiveSupport::TimeZone['Alaska']
assert_equal ActiveSupport::TimeZone['Alaska'], Time.zone
@@ -843,11 +866,27 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
end
def test_time_zone_setter_with_invalid_zone
- Time.zone = 'foo'
- assert_nil Time.zone
+ assert_raise(ArgumentError){ Time.zone = "No such timezone exists" }
+ assert_raise(ArgumentError){ Time.zone = -15.hours }
+ assert_raise(ArgumentError){ Time.zone = Object.new }
+ end
+
+ def test_find_zone_without_bang_returns_nil_if_time_zone_can_not_be_found
+ assert_nil Time.find_zone('No such timezone exists')
+ assert_nil Time.find_zone(-15.hours)
+ assert_nil Time.find_zone(Object.new)
+ end
+
+ def test_find_zone_with_bang_raises_if_time_zone_can_not_be_found
+ assert_raise(ArgumentError) { Time.find_zone!('No such timezone exists') }
+ assert_raise(ArgumentError) { Time.find_zone!(-15.hours) }
+ assert_raise(ArgumentError) { Time.find_zone!(Object.new) }
+ end
- Time.zone = -15.hours
- assert_nil Time.zone
+ def test_time_zone_setter_with_find_zone_without_bang
+ assert_nil Time.zone = Time.find_zone('No such timezone exists')
+ assert_nil Time.zone = Time.find_zone(-15.hours)
+ assert_nil Time.zone = Time.find_zone(Object.new)
end
def test_current_returns_time_now_when_zone_not_set
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index e48425ca25..b215b60df3 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -50,4 +50,30 @@ class OrderedOptionsTest < Test::Unit::TestCase
assert_equal 2, a.size
assert_equal 56, a.else_where
end
+
+ def test_inheritable_options_continues_lookup_in_parent
+ parent = ActiveSupport::OrderedOptions.new
+ parent[:foo] = true
+
+ child = ActiveSupport::InheritableOptions.new(parent)
+ assert child.foo
+ end
+
+ def test_inheritable_options_can_override_parent
+ parent = ActiveSupport::OrderedOptions.new
+ parent[:foo] = :bar
+
+ child = ActiveSupport::InheritableOptions.new(parent)
+ child[:foo] = :baz
+
+ assert_equal :baz, child.foo
+ end
+
+ def test_inheritable_options_inheritable_copy
+ original = ActiveSupport::InheritableOptions.new
+ copy = original.inheritable_copy
+
+ assert copy.kind_of?(original.class)
+ assert_not_equal copy.object_id, original.object_id
+ end
end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index b054855d08..08e11d4f38 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
require 'active_support/inflector/transliterate'
+require 'active_support/core_ext/object/inclusion'
class TransliterateTest < Test::Unit::TestCase
@@ -15,7 +16,7 @@ class TransliterateTest < Test::Unit::TestCase
# create string with range of Unicode"s western characters with
# diacritics, excluding the division and multiplication signs which for
# some reason or other are floating in the middle of all the letters.
- string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include? c}.pack("U*")
+ string = (0xC0..0x17E).to_a.reject {|c| c.in?([0xD7, 0xF7])}.pack("U*")
string.each_char do |char|
assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string)
end
diff --git a/bin/rails b/bin/rails
index a628cc35ca..f9725d78d0 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,9 +1,7 @@
#!/usr/bin/env ruby
-begin
- require "rails/cli"
-rescue LoadError
+if File.exists?(File.join(File.expand_path('../..', __FILE__), '.git'))
railties_path = File.expand_path('../../railties/lib', __FILE__)
$:.unshift(railties_path)
- require "rails/cli"
end
+require "rails/cli"
diff --git a/load_paths.rb b/load_paths.rb
index 8f37364629..15345e31e3 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,32 +1,9 @@
-begin
- require File.expand_path('../.bundle/environment', __FILE__)
-rescue LoadError
- begin
- # bust gem prelude
- if defined? Gem
- Gem.source_index
- gem 'bundler'
- else
- require 'rubygems'
- end
- require 'bundler'
- Bundler.setup
- rescue LoadError
- module Bundler
- def self.require(*args, &block); end
- def self.method_missing(*args, &block); end
- end
-
- %w(
- actionmailer
- actionpack
- activemodel
- activerecord
- activeresource
- activesupport
- railties
- ).each do |framework|
- $:.unshift File.expand_path("../#{framework}/lib", __FILE__)
- end
- end
+# bust gem prelude
+if defined? Gem
+ Gem.source_index
+ gem 'bundler'
+else
+ require 'rubygems'
end
+require 'bundler'
+Bundler.setup \ No newline at end of file
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index f159247308..a4f0d31971 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,25 @@
*Rails 3.1.0 (unreleased)*
+* The scaffold controller will now produce SCSS file if Sass is available [Prem Sichanugrist]
+
+* The controller and resource generators will now automatically produce asset stubs (this can be turned off with --skip-assets). These stubs will use Coffee and Sass, if those libraries are available. [DHH]
+
+* jQuery is the new default JavaScript library. [fxn]
+
+* Changed scaffold and app generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist]
+
+ So instead of creating something like:
+
+ redirect_to users_path, :notice => "User has been created"
+
+ it will now be like this:
+
+ redirect_to users_path, notice: "User has been created"
+
+ You can also passing `--old-style-hash` to make Rails generate old style hash even you're on Ruby 1.9
+
+* Changed scaffold_controller generator to create format block for JSON instead of XML [Prem Sichanugrist]
+
* Add using Turn with natural language test case names for test_help.rb when running with minitest (Ruby 1.9.2+) [DHH]
* Direct logging of Active Record to STDOUT so it's shown inline with the results in the console [DHH]
diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb
index affef1ccb0..352c5e91dd 100644
--- a/railties/guides/rails_guides/textile_extensions.rb
+++ b/railties/guides/rails_guides/textile_extensions.rb
@@ -1,9 +1,11 @@
+require 'active_support/core_ext/object/inclusion'
+
module RailsGuides
module TextileExtensions
def notestuff(body)
body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*)$/) do |m|
css_class = $1.downcase
- css_class = 'warning' if ['caution', 'important'].include?(css_class)
+ css_class = 'warning' if css_class.in?(['caution', 'important'])
result = "<div class='#{css_class}'><p>"
result << $2.strip
@@ -33,7 +35,7 @@ module RailsGuides
def code(body)
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
es = ERB::Util.h($2)
- css_class = ['erb', 'shell'].include?($1) ? 'html' : $1
+ css_class = $1.in?(['erb', 'shell']) ? 'html' : $1
%{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>}
end
end
diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile
index 67743a4797..15abba66ab 100644
--- a/railties/guides/source/2_3_release_notes.textile
+++ b/railties/guides/source/2_3_release_notes.textile
@@ -410,7 +410,7 @@ You're likely familiar with Rails' practice of adding timestamps to static asset
h4. Asset Hosts as Objects
-Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting.
+Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to implement any complex logic you need in your asset hosting.
* More Information: "asset-hosting-with-minimum-ssl":http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 178d98c2d6..496dc7224b 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -615,26 +615,15 @@ Rails comes with two built-in HTTP authentication mechanisms:
h4. HTTP Basic Authentication
-HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +authenticate_or_request_with_http_basic+.
+HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, +http_basic_authenticate_with+.
<ruby>
class AdminController < ApplicationController
- USERNAME, PASSWORD = "humbaba", "5baa61e4"
-
- before_filter :authenticate
-
- private
-
- def authenticate
- authenticate_or_request_with_http_basic do |username, password|
- username == USERNAME &&
- Digest::SHA1.hexdigest(password) == PASSWORD
- end
- end
+ http_basic_authenticate_with :name => "humbaba", :password => "5baa61e4"
end
</ruby>
-With this in place, you can create namespaced controllers that inherit from +AdminController+. The before filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
+With this in place, you can create namespaced controllers that inherit from +AdminController+. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.
h4. HTTP Digest Authentication
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index cfd71ad287..d0b3ee6bfc 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -1295,7 +1295,7 @@ end
h5. update_page_tag
-Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERb template.
+Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERB template.
h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index f3a10b8b92..df8e35ed33 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -790,7 +790,7 @@ Post.includes(:comments).where("comments.visible", true)
This would generate a query which contains a +LEFT OUTER JOIN+ whereas the +joins+ method would generate one using the +INNER JOIN+ function instead.
<ruby>
- SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible)
+ SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1)
</ruby>
If there was no +where+ condition, this would generate the normal set of two queries.
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 514d0322b9..c65dd52e48 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -822,7 +822,7 @@ The selectors to customize the style of error messages are:
* +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element.
* +#errorExplanation ul li+ - Style for the list items with individual error messages.
-Scaffolding for example generates +public/stylesheets/scaffold.css+, which defines the red-based style you saw above.
+Scaffolding for example generates +app/assets/stylesheets/scaffold.css.scss+, which later compiles to +app/assets/stylesheets/scaffold.css+ and defines the red-based style you saw above.
The name of the class and the id can be changed with the +:class+ and +:id+ options, accepted by both helpers.
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 788f528654..3ba840c044 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -442,6 +442,28 @@ require_library_or_gem('mysql')
NOTE: Defined in +active_support/core_ext/kernel/requires.rb+.
+h4. +in?+ and +either?+
+
+The predicate +in?+ tests if an object is included in another object, and the predicate +either?+ tests if an object is included in a list of objects which will be passed as arguments.
+
+Examples of +in?+:
+
+<ruby>
+ 1.in?([1,2]) # => true
+ "lo".in?("hello") # => true
+ 25.in?(30..50) # => false
+</ruby>
+
+Examples of +either?+:
+
+<ruby>
+ 1.either?(1,2,3) # => true
+ 5.either?(1,2,3) # => false
+ [1,2,3].either?([1,2,3], 2, [3,4,5]) # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
+
h3. Extensions to +Module+
h4. +alias_method_chain+
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
index b80df4aa58..38a63ea483 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/railties/guides/source/ajax_on_rails.textile
@@ -3,14 +3,14 @@ h2. AJAX on Rails
This guide covers the built-in Ajax/JavaScript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics:
* Quick introduction to AJAX and related technologies
-* Handling JavaScript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us
+* Handling JavaScript the Rails way: Rails helpers, Prototype and script.aculo.us
* Testing JavaScript functionality
endprologue.
h3. Hello AJAX - a Quick Intro
-If you are a 'show me the code' type of person, you might want to skip this part and jump to the RJS section right away. However, I would really recommend to read it - you'll need the basics of DOM, http requests and other topics discussed here to really understand Ajax on Rails.
+You'll need the basics of DOM, HTTP requests and other topics discussed here to really understand Ajax on Rails.
h4. Asynchronous JavaScript + XML
@@ -62,7 +62,7 @@ link_to_remote "Add to cart",
* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff:
** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too.
-** *:update* There are basically two ways of injecting the server response into the page: One is involving RJS and we will discuss it in the next chapter, and the other is specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
+** *:update* Specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation:
<ruby>
link_to_remote "Add to cart",
@@ -178,12 +178,7 @@ h5. +remote_function+
h5. +update_page+
-
-h3. JavaScript the Rails way: RJS
-
-In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the +:update+ option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript... or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you - so basically RJS is a Ruby DSL to write JavaScript in your Rails code.
-
-h4. JavaScript without RJS
+h4. Serving JavaScript
First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood.
@@ -198,136 +193,6 @@ end
What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior).
-h4. Inline RJS
-
-As we said, the purpose of RJS is to write Ruby which is then auto-magically turned into JavaScript by Rails. The above example didn't look too Ruby-esque so let's see how to do it the Rails way:
-
-<ruby>
-def javascript_test
- render :update do |page|
- page.alert "Hello from inline RJS"
- end
-end
-</ruby>
-
-The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript".
-
-h4. RJS Templates
-
-If you don't want to clutter your controllers with view code (especially when your inline RJS is more than a few lines), you can move your RJS code to a template file. RJS templates should go to the +/app/views/+ directory, just as +.html.erb+ or any other view files of the appropriate controller, conventionally named +js.rjs+.
-
-To rewrite the above example, you can leave the body of the action empty, and create a RJS template named +javascript_test.js.rjs+, containing the following line:
-
-<ruby>
-page.alert "Hello from inline RJS"
-</ruby>
-
-h4. RJS Reference
-
-In this section we'll go through the methods RJS offers.
-
-h5. JavaScriptGenerator Methods
-
-h6. DOM Element Manipulation
-
-It is possible to manipulate multiple elements at once through the +page+ JavaScriptGenerator instance. Let's see this in action:
-
-<ruby>
-page.show :div_one, :div_two
-page.hide :div_one
-page.remove :div_one, :div_two, :div_three
-page.toggle :other_div
-</ruby>
-
-The above methods (+show+, +hide+, +remove+, +toggle+) have the same semantics as the Prototype methods of the same name. You can pass an arbitrary number (but at least one) of DOM ids to these calls.
-
-
-h6. Inserting and Replacing Content
-
-You can insert content into an element on the page with the +insert_html+ method:
-
-<ruby>
-page.insert_html :top, :result, '42'
-</ruby>
-
-The first parameter is the position of the new content relative to the element specified by the second parameter, a DOM id.
-
-Position can be one of these four values:
-
-*** +:before+ Inserts the response text just before the target element.
-*** +:after+ The response is inserted after the target element.
-*** +:top+ Inserts the text into the target element, before it's original content.
-*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content.
-
-The third parameter can either be a string, or a hash of options to be passed to ActionView::Base#render - for example:
-
-<ruby>
-page.insert_html :top, :result, :partial => "the_answer"
-</ruby>
-
-You can replace the contents (innerHTML) of an element with the +replace_html+ method. The only difference is that since it's clear where should the new content go, there is no need for a position parameter - so +replace_html+ takes only two arguments,
-the DOM id of the element you wish to modify and a string or a hash of options to be passed to ActionView::Base#render.
-
-h6. Delay
-
-You can delay the execution of a block of code with +delay+:
-
-<ruby>
-page.delay(10) { page.alert('Hey! Just waited 10 seconds') }
-</ruby>
-
-+delay+ takes one parameter (time to wait in seconds) and a block which will be executed after the specified time has passed - whatever else follows a +page.delay+ line is executed immediately, the delay affects only the code in the block.
-
-h6. Reloading and Redirecting
-
-You can reload the page with the +reload+ method:
-
-<ruby>
-page.reload
-</ruby>
-
-When using AJAX, you can't rely on the standard +redirect_to+ controller method - you have to use the +page+'s instance method, also called +redirect_to+:
-
-<ruby>
-page.redirect_to some_url
-</ruby>
-
-h6. Generating Arbitrary JavaScript
-
-Sometimes even the full power of RJS is not enough to accomplish everything, but you still don't want to drop to pure JavaScript. A nice golden mean is offered by the combination of +<<+, +assign+ and +call+ methods:
-
-<ruby>
- page << "alert('1+1 equals 3')"
-</ruby>
-
-So +<<+ is used to execute an arbitrary JavaScript statement, passed as string to the method. The above code is equivalent to:
-
-<ruby>
- page.assign :result, 3
- page.call :alert, '1+1 equals ' + result
-</ruby>
-
-+assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function.
-
-h6. Class Proxies
-
-h5. Element Proxies
-
-h5. Collection Proxies
-
-h5. RJS Helpers
-
-
-
-h3. I Want my Yellow Thingy: Quick overview of Script.aculo.us
-
-h4. Introduction
-
-h4. Visual Effects
-
-h4. Drag and Drop
-
-
h3. Testing JavaScript
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
index 7433507866..e22ffa4c04 100644
--- a/railties/guides/source/api_documentation_guidelines.textile
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -29,7 +29,7 @@ Documentation has to be concise but comprehensive. Explore and document edge cas
The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. All Rails documentation should consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal.
-Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERb. When in doubt, please have a look at some authoritative source like their official documentation.
+Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. When in doubt, please have a look at some authoritative source like their official documentation.
Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 581fece1ab..9d8a9caf08 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -215,13 +215,13 @@ $ rails generate scaffold HighScore game:string score:integer
create app/views/layouts/
exists test/functional/
create test/unit/
- create public/stylesheets/
+ create app/assets/stylesheets/
create app/views/high_scores/index.html.erb
create app/views/high_scores/show.html.erb
create app/views/high_scores/new.html.erb
create app/views/high_scores/edit.html.erb
create app/views/layouts/high_scores.html.erb
- create public/stylesheets/scaffold.css
+ create app/assets/stylesheets/scaffold.css.scss
create app/controllers/high_scores_controller.rb
create test/functional/high_scores_controller_test.rb
create app/helpers/high_scores_helper.rb
@@ -484,7 +484,7 @@ end
We take whatever args are supplied, save them to an instance variable, and literally copying from the Rails source, implement a +manifest+ method, which calls +record+ with a block, and we:
* Check there's a *public* directory. You bet there is.
-* Run the ERb template called "tutorial.erb".
+* Run the ERB template called "tutorial.erb".
* Save it into "Rails.root/public/tutorial.txt".
* Pass in the arguments we saved through the +:assigns+ parameter.
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 298335d484..53460b8c36 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -149,7 +149,7 @@ h4. Configuring Middleware
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
-* +Rack::SSL+ Will force every requests to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_.
+* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_.
* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is _true_.
* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to _false_, which it is by default.
* +ActiveSupport::Cache::Strategy::LocalCache+ Serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
@@ -291,8 +291,6 @@ h4. Configuring Action View
There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
-* +config.action_view.debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alerts the caught exception (and then re-raises it). The default is +false+.
-
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%&lt;div class=&quot;field_with_errors&quot;&gt;#{html_tag}&lt;/div&gt;).html_safe }</tt>
* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile
index 045b8823ca..fb17dccfb8 100644
--- a/railties/guides/source/debugging_rails_applications.textile
+++ b/railties/guides/source/debugging_rails_applications.textile
@@ -96,26 +96,6 @@ Will be rendered as follows:
Title: Rails debugging guide
</pre>
-h4. Debugging RJS
-
-Rails has optional built-in support to debug RJS. When enabled, responses are wrapped in a try/catch block that displays the caught exception using +alert()+, and then re-raises it.
-
-The flag to enable RJS debugging in your configuration files is +config.action_view.debug_rjs+:
-
-<ruby>
-config.action_view.debug_rjs = true
-</ruby>
-
-or at any time setting +ActionView::Base.debug_rjs+:
-
-<ruby>
-ActionView::Base.debug_rjs = true
-</ruby>
-
-It is enabled by default in development mode, and disabled in the rest.
-
-TIP: For more information on debugging JavaScript, refer to "Firebug":http://getfirebug.com/, the popular debugger for Firefox.
-
h3. The Logger
It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment.
diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile
index d32ba48003..cd8ac3d6fd 100644
--- a/railties/guides/source/generators.textile
+++ b/railties/guides/source/generators.textile
@@ -190,7 +190,7 @@ $ rails generate scaffold User name:string
invoke test_unit
create test/unit/helpers/users_helper_test.rb
invoke stylesheets
- create public/stylesheets/scaffold.css
+ create app/assets/stylesheets/scaffold.css.scss
</shell>
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 0661549644..3f9bd2b6da 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -368,7 +368,7 @@ The scaffold generator will build 15 files in your application, along with some
|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller|
|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper|
|config/routes.rb |Edited to include routing information for posts|
-|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better|
+|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better|
h4. Running a Migration
@@ -501,8 +501,8 @@ def index
@posts = Post.all
respond_to do |format|
- format.html # index.html.erb
- format.xml { render :xml => @posts }
+ format.html # index.html.erb
+ format.json { render :json => @posts }
end
end
</ruby>
@@ -511,7 +511,7 @@ end
TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html.
-The +respond_to+ block handles both HTML and XML calls to this action. If you browse to "http://localhost:3000/posts.xml":http://localhost:3000/posts.xml, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:
+The +respond_to+ block handles both HTML and JSON calls to this action. If you browse to "http://localhost:3000/posts.json":http://localhost:3000/posts.json, you'll see a JSON containing all of the posts. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:
<erb>
<h1>Listing posts</h1>
@@ -584,8 +584,8 @@ def new
@post = Post.new
respond_to do |format|
- format.html # new.html.erb
- format.xml { render :xml => @post }
+ format.html # new.html.erb
+ format.json { render :json => @post }
end
end
</ruby>
@@ -653,13 +653,13 @@ def create
respond_to do |format|
if @post.save
- format.html { redirect_to(@post,
+ format.html { redirect_to(@post,
:notice => 'Post was successfully created.') }
- format.xml { render :xml => @post,
+ format.json { render :json => @post,
:status => :created, :location => @post }
else
- format.html { render :action => "new" }
- format.xml { render :xml => @post.errors,
+ format.html { render :action => "new" }
+ format.json { render :json => @post.errors,
:status => :unprocessable_entity }
end
end
@@ -681,8 +681,8 @@ def show
@post = Post.find(params[:id])
respond_to do |format|
- format.html # show.html.erb
- format.xml { render :xml => @post }
+ format.html # show.html.erb
+ format.json { render :json => @post }
end
end
</ruby>
@@ -743,12 +743,12 @@ def update
respond_to do |format|
if @post.update_attributes(params[:post])
- format.html { redirect_to(@post,
+ format.html { redirect_to(@post,
:notice => 'Post was successfully updated.') }
- format.xml { head :ok }
+ format.json { render :json => {}, :status => :ok }
else
- format.html { render :action => "edit" }
- format.xml { render :xml => @post.errors,
+ format.html { render :action => "edit" }
+ format.json { render :json => @post.errors,
:status => :unprocessable_entity }
end
end
@@ -767,8 +767,8 @@ def destroy
@post.destroy
respond_to do |format|
- format.html { redirect_to(posts_url) }
- format.xml { head :ok }
+ format.html { redirect_to(posts_url) }
+ format.json { render :json => {}, :status => :ok }
end
end
</ruby>
@@ -1201,36 +1201,19 @@ h3. Security
If you were to publish your blog online, anybody would be able to add, edit and delete posts or delete comments.
-Rails provides a very simple HTTP authentication system that will work nicely in this situation. First, we enable simple HTTP based authentication in our <tt>app/controllers/application_controller.rb</tt>:
+Rails provides a very simple HTTP authentication system that will work nicely in this situation.
-<ruby>
-class ApplicationController < ActionController::Base
- protect_from_forgery
-
- private
-
- def authenticate
- authenticate_or_request_with_http_basic do |user_name, password|
- user_name == 'admin' && password == 'password'
- end
- end
-
-end
-</ruby>
-
-You can obviously change the username and password to whatever you want. We put this method inside of +ApplicationController+ so that it is available to all of our controllers.
-
-Then in the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails <tt>before_filter</tt> method, which allows us to specify that Rails must run a method and only then allow access to the requested action if that method allows it.
+In the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails <tt>http_basic_authenticate_with</tt> method, allowing access to the requested action if that method allows it.
-To use the before filter, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that:
+To use the authentication system, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that:
<ruby>
class PostsController < ApplicationController
- before_filter :authenticate, :except => [:index, :show]
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
# GET /posts
- # GET /posts.xml
+ # GET /posts.json
def index
@posts = Post.all
respond_to do |format|
@@ -1242,7 +1225,7 @@ We also only want to allow authenticated users to delete comments, so in the +Co
<ruby>
class CommentsController < ApplicationController
- before_filter :authenticate, :only => :destroy
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
def create
@post = Post.find(params[:post_id])
@@ -1475,6 +1458,7 @@ Two very common sources of data that are not UTF-8:
h3. Changelog
+* April 11, 2011: Changed scaffold_controller generator to create format block for JSON instead of XML "Sebastian Martinez":http://www.wyeworks.com
* August 30, 2010: Minor editing after Rails 3 release by "Joost Baaij":http://www.spacebabies.nl
* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
* May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 0cbbe1f389..7c01f01b24 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -478,8 +478,7 @@ The next line in +config/application.rb+ is:
require 'rails/all'
</ruby>
-h4 +railties/lib/rails/all.rb+
-
+h4. +railties/lib/rails/all.rb+
This file is responsible for requiring all the individual parts of Rails like so:
@@ -591,7 +590,7 @@ h4. +activesupport/lib/active_support/deprecation/behaviors.rb+
This file defines the behavior of the +ActiveSupport::Deprecation+ module, setting up the +DEFAULT_BEHAVIORS+ hash constant which contains the three defaults to outputting deprecation warnings: +:stderr+, +:log+ and +:notify+. This file begins by requiring +activesupport/notifications+ and +activesupport/core_ext/array/wrap+.
-h4 +activesupport/lib/active_support/notifications.rb+
+h4. +activesupport/lib/active_support/notifications.rb+
TODO: document +ActiveSupport::Notifications+.
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index f2681c6461..911655e0f4 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -111,7 +111,7 @@
<h3>Feedback</h3>
<p>
- You're encouraged to help in keeping the quality of this guide.
+ You're encouraged to help improve the quality of this guide.
</p>
<p>
If you see any typos or factual errors you are confident to
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index 1548da0eb5..8dab578e6b 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -90,7 +90,7 @@ If we want to display the properties of all the books in our view, we can do so
<%= link_to 'New book', new_book_path %>
</ruby>
-NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (JavaScript with embedded ruby) and +.builder+ for Builder (XML generator).
+NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), and +.builder+ for Builder (XML generator).
h4. Using +render+
@@ -250,18 +250,6 @@ render :inline =>
"xml.p {'Horrid coding practice!'}", :type => :builder
</ruby>
-h5. Using +render+ with +:update+
-
-You can also render JavaScript-based page updates inline using the +:update+ option to +render+:
-
-<ruby>
-render :update do |page|
- page.replace_html 'warning', "Invalid options supplied"
-end
-</ruby>
-
-WARNING: Placing JavaScript updates in your controller may seem to streamline small updates, but it defeats the MVC orientation of Rails and will make it harder for other developers to follow the logic of your project. We recommend using a separate RJS template instead, no matter how small the update.
-
h5. Rendering Text
You can send plain text - with no markup at all - back to the browser by using the +:text+ option to +render+:
@@ -296,7 +284,7 @@ TIP: You don't need to call +to_xml+ on the object that you want to render. If y
h5. Rendering Vanilla JavaScript
-Rails can render vanilla JavaScript (as an alternative to using +update+ with an +.rjs+ file):
+Rails can render vanilla JavaScript:
<ruby>
render :js => "alert('Hello Rails');"
@@ -707,18 +695,28 @@ To include +http://example.com/main.js+:
<%= javascript_include_tag "http://example.com/main.js" %>
</erb>
-The +defaults+ option loads the Prototype and Scriptaculous libraries:
+The +:defaults+ option loads jQuery by default:
<erb>
<%= javascript_include_tag :defaults %>
</erb>
-The +all+ option loads every JavaScript file in +public/javascripts+, starting with the Prototype and Scriptaculous libraries:
+If the application was generated with "-j prototype" <tt>:defaults</tt> loads Prototype and Scriptaculous. And you can in any case override the expansion in <tt>config/application.rb</tt>:
+
+<ruby>
+config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
+</ruby>
+
+When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in <tt>public/javascripts</tt> it will be included as well at then end.
+
+The +:all+ option loads every JavaScript file in +public/javascripts+:
<erb>
<%= javascript_include_tag :all %>
</erb>
+Note that your defaults of choice will be included first, so they will be available to all subsequently included files.
+
You can supply the +:recursive+ option to load files in subfolders of +public/javascripts+ as well:
<erb>
diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
index 6576758856..8e55780dca 100644
--- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -10,10 +10,10 @@ Guides are written in "Textile":http://www.textism.com/tools/textile/. There's c
h3. Prologue
-Each guide should start with motivational text at the top. That's the little introduction in the blue area. The prologue should tell the readers what's the guide about, and what will they learn. See for example the "Routing Guide":routing.html.
+Each guide should start with motivational text at the top (that's the little introduction in the blue area.) The prologue should tell the reader what the guide is about, and what they will learn. See for example the "Routing Guide":routing.html.
h3. Titles
-
+
The title of every guide uses +h2+, guide sections use +h3+, subsections +h4+, etc.
Capitalize all words except for internal articles, prepositions, conjunctions, and forms of the verb to be:
@@ -23,7 +23,7 @@ h5. Middleware Stack is an Array
h5. When are Objects Saved?
</plain>
-Use same typography as in regular text:
+Use the same typography as in regular text:
<plain>
h6. The +:content_type+ Option
@@ -42,13 +42,13 @@ Those guidelines apply also to guides.
h3. HTML Generation
-To generate all the guides just cd into the +railties+ directory and execute
+To generate all the guides, just +cd+ into the +railties+ directory and execute:
<plain>
bundle exec rake generate_guides
</plain>
-You'll need the gems erubis, i18n, and RedCloth.
+(You may need to run +bundle install+ first to install the required gems.)
To process +my_guide.textile+ and nothing else use the +ONLY+ environment variable:
@@ -56,13 +56,13 @@ To process +my_guide.textile+ and nothing else use the +ONLY+ environment variab
bundle exec rake generate_guides ONLY=my_guide
</plain>
-Although by default guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
+By default, guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
To force process of all the guides, pass +ALL=1+.
-It is also recommended that you work with +WARNINGS=1+, this detects duplicate IDs and warns about broken internal links.
+It is also recommended that you work with +WARNINGS=1+. This detects duplicate IDs and warns about broken internal links.
-If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +LANGUAGE+ environment variable.
+If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +LANGUAGE+ environment variable:
<plain>
rake generate_guides LANGUAGE=es
@@ -70,7 +70,7 @@ rake generate_guides LANGUAGE=es
h3. HTML Validation
-Please do validate the generated HTML with
+Please validate the generated HTML with:
<plain>
rake validate_guides
@@ -80,4 +80,5 @@ Particularly, titles get an ID generated from their content and this often leads
h3. Changelog
+* March 31, 2011: grammar tweaks by "Josiah Ivey":http://twitter.com/josiahivey
* October 5, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 893f65856c..c9dc1c2d7c 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -893,12 +893,6 @@ h4. Ajax Injection
If you use the "in_place_editor plugin":http://dev.rubyonrails.org/browser/plugins/in_place_editing, or actions that return a string, rather than rendering a view, _(highlight)you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.
-h4. RJS Injection
-
--- _Don't forget to escape in JavaScript (RJS) templates, too._
-
-The RJS API generates blocks of JavaScript code based on Ruby code, thus allowing you to manipulate a view or parts of a view from the server side. <em class="highlight">If you allow user input in RJS templates, do escape it using +escape_javascript()+ within JavaScript functions, and in HTML parts using +h()+</em>. Otherwise an attacker could execute arbitrary JavaScript.
-
h4. Command Line Injection
-- _Use user-supplied command line parameters with caution._
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index d3f72509c6..e2317661ea 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -79,9 +79,9 @@ steve:
Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are separated by a blank space. You can place comments in a fixture file by using the # character in the first column.
-h5. ERb'in It Up
+h5. ERB'in It Up
-ERb allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb when you load fixtures. This allows you to use Ruby to help you generate some sample data.
+ERB allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data.
<erb>
<% earth_size = 20 %>
@@ -391,7 +391,7 @@ There are a bunch of different types of assertions you can use. Here's the compl
|+assert_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is true.|
|+assert_not_nil( obj, [msg] )+ |Ensures that +obj.nil?+ is false.|
|+assert_match( regexp, string, [msg] )+ |Ensures that a string matches the regular expression.|
-|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't matches the regular expression.|
+|+assert_no_match( regexp, string, [msg] )+ |Ensures that a string doesn't match the regular expression.|
|+assert_in_delta( expecting, actual, delta, [msg] )+ |Ensures that the numbers +expecting+ and +actual+ are within +delta+ of each other.|
|+assert_throws( symbol, [msg] ) { block }+ |Ensures that the given block throws the symbol.|
|+assert_raise( exception1, exception2, ... ) { block }+ |Ensures that the given block raises one of the given exceptions.|
@@ -592,7 +592,6 @@ There are more assertions that are primarily used in testing views:
|_.Assertion |_.Purpose|
|+assert_select_email+ |Allows you to make assertions on the body of an e-mail. |
-|+assert_select_rjs+ |Allows you to make assertions on an RJS response. +assert_select_rjs+ has variants which allow you to narrow down on the updated element or even a particular operation on an element.|
|+assert_select_encoded+ |Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.|
|+css_select(selector)+ or +css_select(element, selector)+ |Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.|
@@ -748,7 +747,8 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
h3. Brief Note About +Test::Unit+
-Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+ that it is how we can use all the basic assertions in our tests.
+Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+, allowing
+us to use all of the basic assertions in our tests.
NOTE: For more information on +Test::Unit+, refer to "test/unit Documentation":http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 1b834275a7..0c3c7737ea 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -50,6 +50,7 @@ module Rails
end
end
+ attr_accessor :assets
delegate :default_url_options, :default_url_options=, :to => :routes
# This method is called just after an application inherits from Rails::Application,
@@ -116,13 +117,10 @@ module Rails
self
end
- alias :build_middleware_stack :app
-
def env_config
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.asset_path" => nil,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
})
end
@@ -139,9 +137,7 @@ module Rails
protected
- def default_asset_path
- nil
- end
+ alias :build_middleware_stack :app
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
@@ -156,8 +152,7 @@ module Rails
end
if config.serve_static_assets
- asset_paths = ActiveSupport::OrderedHash[config.static_asset_paths.to_a.reverse]
- middleware.use ::ActionDispatch::Static, asset_paths
+ middleware.use ::ActionDispatch::Static, paths["public"].first
end
middleware.use ::Rack::Lock unless config.allow_concurrency
@@ -205,4 +200,4 @@ module Rails
require "rails/console/helpers"
end
end
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 23b0e765ae..e5476fbe7a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -4,12 +4,12 @@ require 'rails/engine/configuration'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :cache_classes, :cache_store,
- :encoding, :consider_all_requests_local, :dependency_loading,
- :filter_parameters, :helpers_paths, :logger,
- :preload_frameworks, :reload_plugins,
- :secret_token, :serve_static_assets, :session_options,
- :time_zone, :whiny_nils, :force_ssl
+ attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
+ :cache_classes, :cache_store, :consider_all_requests_local,
+ :dependency_loading, :encoding, :filter_parameters,
+ :force_ssl, :helpers_paths, :logger, :preload_frameworks,
+ :reload_plugins, :secret_token, :serve_static_assets,
+ :session_options, :time_zone, :whiny_nils
attr_writer :log_level
@@ -29,6 +29,12 @@ module Rails
@log_level = nil
@middleware = app_middleware
@generators = app_generators
+
+ @assets = ActiveSupport::OrderedOptions.new
+ @assets.enabled = false
+ @assets.paths = []
+ @assets.precompile = []
+ @assets.prefix = "/assets"
end
def compiled_asset_path
@@ -56,6 +62,9 @@ module Rails
paths.add "config/environment", :with => "config/environment.rb"
paths.add "lib/templates"
paths.add "log", :with => "log/#{Rails.env}.log"
+ paths.add "public"
+ paths.add "public/javascripts"
+ paths.add "public/stylesheets"
paths.add "tmp"
paths.add "tmp/cache"
paths
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index a45b61c99c..bf865ce466 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -53,6 +53,8 @@ module Rails
end
# Force routes to be loaded just at the end and add it to to_prepare callbacks
+ # This needs to be after the finisher hook to ensure routes added in the hook
+ # are still loaded.
initializer :set_routes_reloader do |app|
reloader = lambda { app.routes_reloader.execute_if_updated }
reloader.call
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 02ccdf8060..4182757346 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
ARGV << '--help' if ARGV.empty?
aliases = {
@@ -69,7 +71,7 @@ when '--version', '-v'
require 'rails/commands/application'
else
- puts "Error: Command not recognized" unless %w(-h --help).include?(command)
+ puts "Error: Command not recognized" unless command.in?(['-h', '--help'])
puts <<-EOT
Usage: rails COMMAND [ARGS]
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 3b57b925ba..f3fa1fd54d 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -1,5 +1,6 @@
require 'rails/version'
-if %w(--version -v).include? ARGV.first
+
+if ['--version', '-v'].include?(ARGV.first)
puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb
index 0432261802..f230f405c0 100644
--- a/railties/lib/rails/commands/benchmarker.rb
+++ b/railties/lib/rails/commands/benchmarker.rb
@@ -1,4 +1,6 @@
-if [nil, "-h", "--help"].include?(ARGV.first)
+require 'active_support/core_ext/object/inclusion'
+
+if ARGV.first.in?([nil, "-h", "--help"])
puts "Usage: rails benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ..."
exit 1
end
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
index db59cd8ad9..2a84e2a6df 100644
--- a/railties/lib/rails/commands/destroy.rb
+++ b/railties/lib/rails/commands/destroy.rb
@@ -1,7 +1,9 @@
require 'rails/generators'
+require 'active_support/core_ext/object/inclusion'
+
Rails::Generators.configure!
-if [nil, "-h", "--help"].include?(ARGV.first)
+if ARGV.first.in?([nil, "-h", "--help"])
Rails::Generators.help 'destroy'
exit
end
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 1b3eef504a..28c1c56352 100644
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -1,7 +1,9 @@
require 'rails/generators'
+require 'active_support/core_ext/object/inclusion'
+
Rails::Generators.configure!
-if [nil, "-h", "--help"].include?(ARGV.first)
+if ARGV.first.in?([nil, "-h", "--help"])
Rails::Generators.help 'generate'
exit
end
diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb
index 6d9717b5cd..7959d8a981 100644
--- a/railties/lib/rails/commands/profiler.rb
+++ b/railties/lib/rails/commands/profiler.rb
@@ -1,4 +1,6 @@
-if [nil, "-h", "--help"].include?(ARGV.first)
+require 'active_support/core_ext/object/inclusion'
+
+if ARGV.first.in?([nil, "-h", "--help"])
$stderr.puts "Usage: rails profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]"
exit(1)
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index ee265366ff..87385814f7 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -171,32 +171,6 @@ module Rails
#
# Now, +Engine+ will get only requests that were not handled by +Application+.
#
- # == Asset path
- #
- # When you use +Engine+ with its own public directory, you will probably want to copy or symlink it
- # to application's public directory. To simplify generating paths for assets, you can set <tt>asset_path</tt>
- # for an engine:
- #
- # module MyEngine
- # class Engine < Rails::Engine
- # config.asset_path = "/my_engine/%s"
- # end
- # end
- #
- # With such a config, asset paths will be automatically modified inside +Engine+:
- #
- # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg"
- #
- # == Serving static files
- #
- # By default, Rails uses <tt>ActionDispatch::Static</tt> to serve static files in development mode. This is ok
- # while you develop your application, but when you want to deploy it, assets from an engine will not be
- # served by default. You should choose one of the two following strategies:
- #
- # * enable serving static files by setting config.serve_static_assets to true
- # * copy engine's public files to application's public folder with <tt>rake ENGINE_NAME:install:assets</tt>, for example
- # <tt>rake my_engine:install:assets</tt>
- #
# == Engine name
#
# There are some places where an Engine's name is used:
@@ -427,8 +401,7 @@ module Rails
def env_config
@env_config ||= {
- 'action_dispatch.routes' => routes,
- 'action_dispatch.asset_path' => config.asset_path
+ 'action_dispatch.routes' => routes
}
end
@@ -509,13 +482,9 @@ module Rails
require environment if environment
end
- initializer :append_asset_paths do
- config.asset_path ||= default_asset_path
-
- public_path = paths["public"].first
- if config.compiled_asset_path && File.exist?(public_path)
- config.static_asset_paths[config.compiled_asset_path] = public_path
- end
+ initializer :append_assets_path do |app|
+ app.config.assets.paths.unshift *paths["vendor/assets"].existent
+ app.config.assets.paths.unshift *paths["app/assets"].existent
end
initializer :prepend_helpers_path do |app|
@@ -537,42 +506,29 @@ module Rails
rake_tasks do
next if self.is_a?(Rails::Application)
+ next unless has_migrations?
namespace railtie_name do
- desc "Shortcut for running both rake #{railtie_name}:install:migrations and #{railtie_name}:install:assets"
- task :install do
- Rake::Task["#{railtie_name}:install:migrations"].invoke
- Rake::Task["#{railtie_name}:install:assets"].invoke
- end
-
namespace :install do
- # TODO Add assets copying to this list
- # TODO Skip this if there is no paths["db/migrate"] for the engine
desc "Copy migrations from #{railtie_name} to application"
task :migrations do
ENV["FROM"] = railtie_name
Rake::Task["railties:install:migrations"].invoke
end
-
- desc "Copy assets from #{railtie_name} to application"
- task :assets do
- ENV["FROM"] = railtie_name
- Rake::Task["railties:install:assets"].invoke
- end
end
end
end
protected
- def default_asset_path
- "/#{railtie_name}%s"
- end
-
def routes?
defined?(@routes)
end
+ def has_migrations?
+ paths["db/migrate"].first.present?
+ end
+
def find_root_with_flag(flag, default=nil)
root_path = self.class.called_from
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 4f458b0aee..241db4b0a9 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -5,7 +5,7 @@ module Rails
class Configuration < ::Rails::Railtie::Configuration
attr_reader :root
attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths
- attr_accessor :plugins, :asset_path
+ attr_accessor :plugins
def initialize(root=nil)
super()
@@ -40,6 +40,7 @@ module Rails
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
paths.add "app", :eager_load => true, :glob => "*"
+ paths.add "app/assets", :glob => "*"
paths.add "app/controllers", :eager_load => true
paths.add "app/helpers", :eager_load => true
paths.add "app/models", :eager_load => true
@@ -55,10 +56,8 @@ module Rails
paths.add "db"
paths.add "db/migrate"
paths.add "db/seeds", :with => "db/seeds.rb"
- paths.add "public"
- paths.add "public/javascripts"
- paths.add "public/stylesheets"
paths.add "vendor", :load_path => true
+ paths.add "vendor/assets", :glob => "*"
paths.add "vendor/plugins"
paths
end
@@ -79,10 +78,6 @@ module Rails
def autoload_paths
@autoload_paths ||= paths.autoload_paths
end
-
- def compiled_asset_path
- asset_path % "" if asset_path
- end
end
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 29e693dfb0..9be395e989 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -24,9 +24,12 @@ module Rails
:rails => {
:actions => '-a',
:orm => '-o',
+ :javascripts => '-j',
+ :javascript_engine => '-je',
:resource_controller => '-c',
:scaffold_controller => '-c',
:stylesheets => '-y',
+ :stylesheet_engine => '-se',
:template_engine => '-e',
:test_framework => '-t'
},
@@ -43,14 +46,18 @@ module Rails
DEFAULT_OPTIONS = {
:rails => {
+ :assets => true,
:force_plural => false,
:helper => true,
- :orm => nil,
:integration_tool => nil,
+ :javascripts => true,
+ :javascript_engine => nil,
+ :orm => nil,
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
+ :stylesheet_engine => nil,
:test_framework => nil,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index a2eaf7a6fb..481fa95068 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -10,7 +10,7 @@ module Rails
module Generators
class AppBase < Base
DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db )
- JAVASCRIPTS = %w( prototype jquery )
+ JAVASCRIPTS = %w( jquery prototype )
attr_accessor :rails_template
add_shebang_option!
@@ -36,11 +36,11 @@ module Rails
class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3",
:desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype",
- :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})"
+ class_option :javascript, :type => :string, :aliases => "-j", :default => "jquery",
+ :desc => "Preconfigure for selected JavaScript library (options: #{JAVASCRIPTS.join('/')})"
class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
- :desc => "Skip javascript files"
+ :desc => "Skip JavaScript files"
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -53,6 +53,9 @@ module Rails
class_option :help, :type => :boolean, :aliases => "-h", :group => :rails,
:desc => "Show this help message and quit"
+
+ class_option :old_style_hash, :type => :boolean, :default => false,
+ :desc => "Force using old style hash (:foo => 'bar') on Ruby >= 1.9"
end
def initialize(*args)
@@ -121,30 +124,33 @@ module Rails
entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'"
end
end
- entry
+ entry + "\n"
end
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
- gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'arel', :git => 'git://github.com/rails/arel.git'
- gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git"
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
- gem 'rails', :git => 'git://github.com/rails/rails.git'
- gem 'arel', :git => 'git://github.com/rails/arel.git'
- gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'rails', :git => 'git://github.com/rails/rails.git'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
+ gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git"
GEMFILE
else
<<-GEMFILE.strip_heredoc
gem 'rails', '#{Rails::VERSION::STRING}'
# Bundle edge Rails instead:
- # gem 'rails', :git => 'git://github.com/rails/rails.git'
- # gem 'arel', :git => 'git://github.com/rails/arel.git'
- # gem 'rack', :git => 'git://github.com/rack/rack.git'
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
+ # gem 'arel', :git => 'git://github.com/rails/arel.git'
+ # gem 'rack', :git => 'git://github.com/rack/rack.git'
+ # gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git"
GEMFILE
end
end
@@ -159,6 +165,25 @@ module Rails
else options[:database]
end
end
+
+ def gem_for_ruby_debugger
+ if RUBY_VERSION < "1.9.2"
+ "gem 'ruby-debug'"
+ else
+ "gem 'ruby-debug19', :require => 'ruby-debug'"
+ end
+ end
+
+ def gem_for_turn
+ unless RUBY_VERSION < "1.9.2"
+ <<-GEMFILE.strip_heredoc
+ group :test do
+ # Pretty printed test output
+ gem 'turn', :require => false
+ end
+ GEMFILE
+ end
+ end
def bundle_if_dev_or_edge
bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
@@ -171,9 +196,22 @@ module Rails
def empty_directory_with_gitkeep(destination, config = {})
empty_directory(destination, config)
+ git_keep(destination)
+ end
+
+ def git_keep(destination)
create_file("#{destination}/.gitkeep") unless options[:skip_git]
end
+ # Returns Ruby 1.9 style key-value pair if current code is running on
+ # Ruby 1.9.x. Returns the old-style (with hash rocket) otherwise.
+ def key_value(key, value)
+ if options[:old_style_hash] || RUBY_VERSION < '1.9'
+ ":#{key} => #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index dfecd2a6e4..8d03cb911b 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -8,6 +8,7 @@ rescue LoadError
end
require 'rails/generators/actions'
+require 'active_support/core_ext/object/inclusion'
module Rails
module Generators
@@ -164,7 +165,7 @@ module Rails
names.each do |name|
defaults = if options[:type] == :boolean
{ }
- elsif [true, false].include?(default_value_for_option(name, options))
+ elsif default_value_for_option(name, options).in?([true, false])
{ :banner => "" }
else
{ :desc => "#{name.to_s.humanize} to be invoked", :banner => "NAME" }
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 4c46db4d67..a7393cfe18 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -17,7 +17,7 @@
<% end -%>
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
- <td><%%= link_to 'Destroy', <%= singular_table_name %>, :confirm => 'Are you sure?', :method => :delete %></td>
+ <td><%%= link_to 'Destroy', <%= singular_table_name %>, <%= key_value :confirm, "'Are you sure?'" %>, <%= key_value :method, ":delete" %> %></td>
</tr>
<%% end %>
</table>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 64273e4ca4..b26161f1d0 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -1,4 +1,5 @@
require 'active_support/time'
+require 'active_support/core_ext/object/inclusion'
module Rails
module Generators
@@ -44,7 +45,7 @@ module Rails
end
def reference?
- [ :references, :belongs_to ].include?(self.type)
+ self.type.in?([:references, :belongs_to])
end
end
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 2af7f85463..36bc9e055c 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -8,6 +8,9 @@ module Rails
class_option :skip_namespace, :type => :boolean, :default => false,
:desc => "Skip namespace (affects only isolated applications)"
+ class_option :old_style_hash, :type => :boolean, :default => false,
+ :desc => "Force using old style hash (:foo => 'bar') on Ruby >= 1.9"
+
def initialize(args, *options) #:nodoc:
# Unfreeze name in case it's given as a frozen string
args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
@@ -181,6 +184,16 @@ module Rails
class_collisions "#{options[:prefix]}#{name}#{options[:suffix]}"
end
end
+
+ # Returns Ruby 1.9 style key-value pair if current code is running on
+ # Ruby 1.9.x. Returns the old-style (with hash rocket) otherwise.
+ def key_value(key, value)
+ if options[:old_style_hash] || RUBY_VERSION < '1.9'
+ ":#{key} => #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 3f6ff35a86..4df68d67c7 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -46,6 +46,8 @@ module Rails
def app
directory 'app'
+ git_keep 'app/mailers'
+ git_keep 'app/models'
end
def config
@@ -80,14 +82,7 @@ module Rails
end
def log
- empty_directory "log"
-
- inside "log" do
- %w( server production development test ).each do |file|
- create_file "#{file}.log"
- chmod "#{file}.log", 0666, :verbose => false
- end
- end
+ empty_directory_with_gitkeep "log"
end
def public_directory
@@ -98,27 +93,6 @@ module Rails
directory "public/images"
end
- def stylesheets
- empty_directory_with_gitkeep "public/stylesheets"
- end
-
- def javascripts
- empty_directory "public/javascripts"
-
- unless options[:skip_javascript]
- copy_file "public/javascripts/#{options[:javascript]}.js"
- copy_file "public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
-
- if options[:javascript] == "prototype"
- copy_file "public/javascripts/controls.js"
- copy_file "public/javascripts/dragdrop.js"
- copy_file "public/javascripts/effects.js"
- end
- end
-
- copy_file "public/javascripts/application.js"
- end
-
def script
directory "script" do |content|
"#{shebang}\n" + content
@@ -127,19 +101,44 @@ module Rails
end
def test
- directory "test"
+ empty_directory_with_gitkeep "test/fixtures"
+ empty_directory_with_gitkeep "test/functional"
+ empty_directory_with_gitkeep "test/integration"
+ empty_directory_with_gitkeep "test/unit"
+
+ template "test/performance/browsing_test.rb"
+ template "test/test_helper.rb"
end
def tmp
- empty_directory "tmp"
+ empty_directory_with_gitkeep "tmp/cache"
+ end
+
+ def vendor
+ vendor_javascripts
+ vendor_stylesheets
+ vendor_plugins
+ end
- inside "tmp" do
- %w(sessions sockets cache pids).each do |dir|
- empty_directory(dir)
+ def vendor_javascripts
+ if options[:skip_javascript]
+ empty_directory_with_gitkeep "vendor/assets/javascripts"
+ else
+ copy_file "vendor/assets/javascripts/#{options[:javascript]}.js"
+ copy_file "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
+
+ if options[:javascript] == "prototype"
+ copy_file "vendor/assets/javascripts/controls.js"
+ copy_file "vendor/assets/javascripts/dragdrop.js"
+ copy_file "vendor/assets/javascripts/effects.js"
end
end
end
+ def vendor_stylesheets
+ empty_directory_with_gitkeep "vendor/assets/stylesheets"
+ end
+
def vendor_plugins
empty_directory_with_gitkeep "vendor/plugins"
end
@@ -150,15 +149,14 @@ module Rails
# can change in Ruby 1.8.7 when we FileUtils.cd.
RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__))
- RESERVED_NAMES = %w[application destroy benchmarker profiler
- plugin runner test]
+ RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test]
class AppGenerator < AppBase
add_shared_options_for "application"
# Add bin/rails options
- class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
- :desc => "Show Rails version number and quit"
+ class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
+ :desc => "Show Rails version number and quit"
def initialize(*args)
raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank?
@@ -168,7 +166,7 @@ module Rails
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
-
+
if !options[:skip_javascript] && !JAVASCRIPTS.include?(options[:javascript])
raise Error, "Invalid value for --javascript option. Supported for preconfiguration are: #{JAVASCRIPTS.join(", ")}."
end
@@ -225,14 +223,6 @@ module Rails
build(:images)
end
- def create_public_stylesheets_files
- build(:stylesheets)
- end
-
- def create_javascript_files
- build(:javascripts)
- end
-
def create_script_files
build(:script)
end
@@ -246,7 +236,7 @@ module Rails
end
def create_vendor_files
- build(:vendor_plugins)
+ build(:vendor)
end
def finish_template
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index c383d4842f..0cee7deb72 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -4,28 +4,18 @@ source 'http://rubygems.org'
<%= database_gemfile_entry -%>
+# Asset template engines
+<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
+gem 'sass', '~> 3.1.0.alpha'
+gem 'coffee-script'
+
# Use unicorn as the web server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
-# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
-# gem 'ruby-debug'
-# gem 'ruby-debug19', :require => 'ruby-debug'
-
-# Bundle the extra gems:
-# gem 'bj'
-# gem 'nokogiri'
-# gem 'sqlite3'
-# gem 'rack-bug', :require => 'rack/bug'
-
-# Bundle gems for the local environment. Make sure to
-# put test-only gems in this group so their generators
-# and rake tasks are available in development mode:
-# group :development, :test do
-# gem 'webrat'
-# end
+# To use debugger
+# <%= gem_for_ruby_debugger %>
-# Needed for guides generation
-# gem "RedCloth", "~> 4.2"
+<%= gem_for_turn -%> \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
new file mode 100644
index 0000000000..fb5e91caf4
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -0,0 +1,11 @@
+// FIXME: Tell people that this is a manifest file, real code should go into discrete files
+// FIXME: Tell people how Sprockets and CoffeeScript works
+//
+//= require <%= options[:javascript] %>
+//= require <%= options[:javascript] %>_ujs
+<% if options[:javascript] == "prototype" %>
+//= require controls
+//= require dragdrop
+//= require effects
+<% end -%>
+//= require_tree .
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
new file mode 100644
index 0000000000..ccfff11a5d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -0,0 +1,4 @@
+/*
+ * FIXME: Introduce SCSS & Sprockets
+ *= require_tree .
+*/ \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 6d56c331c1..c63d1b6ac5 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -2,8 +2,8 @@
<html>
<head>
<title><%= camelized %></title>
- <%%= stylesheet_link_tag :all %>
- <%%= javascript_include_tag :defaults %>
+ <%%= stylesheet_link_tag "application" %>
+ <%%= javascript_include_tag "application" %>
<%%= csrf_meta_tags %>
</head>
<body>
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 b7f64af339..ff8e6e5f3e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -42,10 +42,10 @@ module <%= app_const_base %>
# JavaScript files you want as :defaults (application.js is always included).
<% if options[:skip_javascript] -%>
config.action_view.javascript_expansions[:defaults] = %w()
-<% elsif options[:javascript] == 'jquery' -%>
- config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+<% elsif options[:javascript] == 'prototype' -%>
+ config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
<% else -%>
- # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+ # config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
<% end -%>
<% if options[:skip_test_unit] -%>
@@ -62,5 +62,8 @@ module <%= app_const_base %>
# Enable IdentityMap for Active Record, to disable set to false or remove the line below.
config.active_record.identity_map = true
<% end -%>
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index bdb897ad33..41b2374eda 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -11,7 +11,6 @@
# Show full error reports and disable caching
config.consider_all_requests_local = true
- config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
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 62aa06dc3e..ddfe4ba1e1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-<%= app_const %>.config.session_store :cookie_store, :key => '_<%= app_name %>_session'
+<%= app_const %>.config.session_store :cookie_store, <%= key_value :key, "'_#{app_name}_session'" %>
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb
index 664d8c74c8..9a2efa68a7 100644
--- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb
+++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb
@@ -3,5 +3,5 @@
#
# Examples:
#
-# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
-# Mayor.create(:name => 'Daley', :city => cities.first)
+# cities = City.create([{ <%= key_value :name, "'Chicago'" %> }, { <%= key_value :name, "'Copenhagen'" %> }])
+# Mayor.create(<%= key_value :name, "'Daley'" %>, <%= key_value :city, "cities.first" %>)
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js
deleted file mode 100644
index fe4577696b..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Place your application-specific JavaScript functions and classes here
-// This file is automatically included by javascript_include_tag :defaults
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index a8f7aeac7d..a8f7aeac7d 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
index 7392fb664c..7392fb664c 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
index 15c6dbca68..15c6dbca68 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
index c81e6c7d5f..c81e6c7d5f 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
index aa3a4f34fd..aa3a4f34fd 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
index 4dcb3779a2..4dcb3779a2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
index 474b2231bb..474b2231bb 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
index 2cd1220786..2cd1220786 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js
+++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
diff --git a/railties/lib/rails/generators/rails/assets/USAGE b/railties/lib/rails/generators/rails/assets/USAGE
new file mode 100644
index 0000000000..adebfd7e6f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/USAGE
@@ -0,0 +1,20 @@
+Description:
+ Stubs out a new asset placeholders. Pass the asset name, either CamelCased
+ or under_scored.
+
+ To create assets within a folder, specify the assets name as a
+ path like 'parent/name'.
+
+ This generates a JavaScript stub in app/assets/javascripts and a stylesheet
+ stub in app/assets/stylesheets.
+
+ If CoffeeScript is available, JavaScripts will be generated with the .coffee extension.
+ If Sass 3 is available, stylesheets will be generated with the .scss extension.
+
+Example:
+ `rails generate assets posts`
+
+ Posts assets.
+ Javascript: app/assets/javascripts/posts.js
+ Stylesheet: app/assets/stylesheets/posts.css
+
diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb
new file mode 100644
index 0000000000..80beb7abfe
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb
@@ -0,0 +1,39 @@
+module Rails
+ module Generators
+ class AssetsGenerator < NamedBase
+ class_option :javascripts, :type => :boolean, :desc => "Generate javascripts"
+ class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
+
+ class_option :javascript_engine, :desc => "Engine for javascripts"
+ class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+
+ def create_javascript_files
+ return unless options.javascripts?
+ copy_file "javascript.#{javascript_extension}",
+ File.join('app/assets/javascripts', class_path, "#{asset_name}.#{javascript_extension}")
+ end
+
+ def create_stylesheet_files
+ return unless options.stylesheets?
+ copy_file "stylesheet.#{stylesheet_extension}",
+ File.join('app/assets/stylesheets', class_path, "#{asset_name}.#{stylesheet_extension}")
+ end
+
+ protected
+
+ def asset_name
+ file_name
+ end
+
+ def javascript_extension
+ options.javascript_engine.present? ?
+ "js.#{options.javascript_engine}" : "js"
+ end
+
+ def stylesheet_extension
+ options.stylesheet_engine.present? ?
+ "css.#{options.stylesheet_engine}" : "css"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js b/railties/lib/rails/generators/rails/assets/templates/javascript.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
new file mode 100644
index 0000000000..09b2da094a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
@@ -0,0 +1,3 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
+// You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
new file mode 100644
index 0000000000..7594abf268
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
@@ -0,0 +1,4 @@
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+*/
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss
new file mode 100644
index 0000000000..ba95e217cc
--- /dev/null
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css.scss
@@ -0,0 +1,5 @@
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+ You can use Sass (SCSS) here: http://sass-lang.com/
+*/
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 9788c0d0bc..74aa0432a8 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -14,7 +14,7 @@ module Rails
end
end
- hook_for :template_engine, :test_framework, :helper
+ hook_for :template_engine, :test_framework, :helper, :assets
end
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 3cf8410d1e..81563f81d3 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -9,10 +9,15 @@ module Rails
end
def app
- if options[:mountable]
+ if mountable?
directory "app"
template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt",
"app/views/layouts/#{name}/application.html.erb"
+ elsif full?
+ empty_directory_with_gitkeep "app/models"
+ empty_directory_with_gitkeep "app/controllers"
+ empty_directory_with_gitkeep "app/views"
+ empty_directory_with_gitkeep "app/helpers"
end
end
@@ -61,8 +66,12 @@ task :default => :test
end
end
+ PASSTHROUGH_OPTIONS = [
+ :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
+ ]
+
def generate_test_dummy(force = false)
- opts = (options || {}).slice(:skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip)
+ opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
opts[:force] = force
invoke Rails::Generators::AppGenerator,
@@ -94,26 +103,36 @@ task :default => :test
end
def stylesheets
- empty_directory_with_gitkeep "public/stylesheets" if options[:mountable]
+ if mountable?
+ copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css",
+ "app/assets/stylesheets/application.css"
+ elsif full?
+ empty_directory_with_gitkeep "app/assets/stylesheets"
+ end
end
def javascripts
- return unless options[:mountable]
+ return if options.skip_javascript?
- empty_directory "#{app_templates_dir}/public/javascripts"
-
- unless options[:skip_javascript]
- copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}.js", "public/javascripts/#{options[:javascript]}.js"
- copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
+ if mountable?
+ copy_file "#{app_templates_dir}/app/assets/javascripts/application.js.tt",
+ "app/assets/javascripts/application.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}.js",
+ "vendor/assets/javascripts/#{options[:javascript]}.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}_ujs.js",
+ "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
if options[:javascript] == "prototype"
- copy_file "#{app_templates_dir}/public/javascripts/controls.js", "public/javascripts/controls.js"
- copy_file "#{app_templates_dir}/public/javascripts/dragdrop.js", "public/javascripts/dragdrop.js"
- copy_file "#{app_templates_dir}/public/javascripts/effects.js", "public/javascripts/effects.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/controls.js",
+ "vendor/assets/javascripts/controls.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/dragdrop.js",
+ "vendor/assets/javascripts/dragdrop.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/effects.js",
+ "vendor/assets/javascripts/effects.js"
end
+ elsif full?
+ empty_directory_with_gitkeep "app/assets/javascripts"
end
-
- copy_file "#{app_templates_dir}/public/javascripts/application.js", "public/javascripts/application.js"
end
def script(force = false)
@@ -130,17 +149,17 @@ task :default => :test
alias_method :plugin_path, :app_path
- class_option :dummy_path, :type => :string, :default => "test/dummy",
- :desc => "Create dummy application at given path"
+ class_option :dummy_path, :type => :string, :default => "test/dummy",
+ :desc => "Create dummy application at given path"
- class_option :full, :type => :boolean, :default => false,
- :desc => "Generate rails engine with integration tests"
+ class_option :full, :type => :boolean, :default => false,
+ :desc => "Generate rails engine with integration tests"
- class_option :mountable, :type => :boolean, :default => false,
- :desc => "Generate mountable isolated application"
+ class_option :mountable, :type => :boolean, :default => false,
+ :desc => "Generate mountable isolated application"
- class_option :skip_gemspec, :type => :boolean, :default => false,
- :desc => "Skip gemspec file"
+ class_option :skip_gemspec, :type => :boolean, :default => false,
+ :desc => "Skip gemspec file"
def initialize(*args)
raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank?
@@ -200,6 +219,7 @@ task :default => :test
public_task :apply_rails_template, :bundle_if_dev_or_edge
protected
+
def app_templates_dir
"../../app/templates"
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 779f933785..6eef0dbe5b 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -6,8 +6,27 @@ module Rails
remove_hook_for :resource_controller
remove_class_option :actions
+ class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets"
+ class_option :stylesheet_engine, :desc => "Engine for stylesheets"
+
hook_for :scaffold_controller, :required => true
- hook_for :stylesheets
+
+ def copy_stylesheets_file
+ if behavior == :invoke && options.stylesheets?
+ template "scaffold.#{stylesheet_extension}", "app/assets/stylesheets/scaffold.#{stylesheet_extension}"
+ end
+ end
+
+ hook_for :assets do |assets|
+ invoke assets, [controller_name]
+ end
+
+ private
+
+ def stylesheet_extension
+ options.stylesheet_engine.present? ?
+ "css.#{options.stylesheet_engine}" : "css"
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 1ae7000299..1ae7000299 100644
--- a/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss
new file mode 100644
index 0000000000..45116b53f6
--- /dev/null
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss
@@ -0,0 +1,58 @@
+body { background-color: #fff; color: #333; }
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a {
+ color: #000;
+ &:visited { color: #666; }
+ &:hover { color: #fff; background-color:#000; }
+}
+
+div.field, div.actions {
+ margin-bottom: 10px;
+}
+
+#notice {
+ color: green;
+}
+
+.field_with_errors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#error_explanation {
+ width: 450px;
+ border: 2px solid red;
+ padding: 7px;
+ padding-bottom: 0;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+
+ h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ margin-bottom: 0px;
+ background-color: #c00;
+ color: #fff;
+ }
+
+ ul li {
+ font-size: 12px;
+ list-style: square;
+ }
+} \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index b5317a055b..32b961d9fc 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,35 +1,35 @@
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
# GET <%= route_url %>
- # GET <%= route_url %>.xml
+ # GET <%= route_url %>.json
def index
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
respond_to do |format|
format.html # index.html.erb
- format.xml { render :xml => @<%= plural_table_name %> }
+ format.json { render <%= key_value :json, "@#{plural_table_name}" %> }
end
end
# GET <%= route_url %>/1
- # GET <%= route_url %>/1.xml
+ # GET <%= route_url %>/1.json
def show
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
format.html # show.html.erb
- format.xml { render :xml => @<%= singular_table_name %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
end
end
# GET <%= route_url %>/new
- # GET <%= route_url %>/new.xml
+ # GET <%= route_url %>/new.json
def new
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
respond_to do |format|
format.html # new.html.erb
- format.xml { render :xml => @<%= singular_table_name %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
end
end
@@ -39,46 +39,46 @@ class <%= controller_class_name %>Controller < ApplicationController
end
# POST <%= route_url %>
- # POST <%= route_url %>.xml
+ # POST <%= route_url %>.json
def create
@<%= singular_table_name %> = <%= orm_class.build(class_name, "params[:#{singular_table_name}]") %>
respond_to do |format|
if @<%= orm_instance.save %>
- format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully created.') }
- format.xml { render :xml => @<%= singular_table_name %>, :status => :created, :location => @<%= singular_table_name %> }
+ format.html { redirect_to @<%= singular_table_name %>, <%= key_value :notice, "'#{human_name} was successfully created.'" %> }
+ format.json { render <%= key_value :json, "@#{singular_table_name}" %>, <%= key_value :status, ':created' %>, <%= key_value :location, "@#{singular_table_name}" %> }
else
- format.html { render :action => "new" }
- format.xml { render :xml => @<%= orm_instance.errors %>, :status => :unprocessable_entity }
+ format.html { render <%= key_value :action, '"new"' %> }
+ format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
end
end
end
# PUT <%= route_url %>/1
- # PUT <%= route_url %>/1.xml
+ # PUT <%= route_url %>/1.json
def update
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
if @<%= orm_instance.update_attributes("params[:#{singular_table_name}]") %>
- format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully updated.') }
- format.xml { head :ok }
+ format.html { redirect_to @<%= singular_table_name %>, <%= key_value :notice, "'#{human_name} was successfully updated.'" %> }
+ format.json { head :ok }
else
- format.html { render :action => "edit" }
- format.xml { render :xml => @<%= orm_instance.errors %>, :status => :unprocessable_entity }
+ format.html { render <%= key_value :action, '"edit"' %> }
+ format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
end
end
end
# DELETE <%= route_url %>/1
- # DELETE <%= route_url %>/1.xml
+ # DELETE <%= route_url %>/1.json
def destroy
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
@<%= orm_instance.destroy %>
respond_to do |format|
- format.html { redirect_to(<%= index_helper %>_url) }
- format.xml { head :ok }
+ format.html { redirect_to <%= index_helper %>_url }
+ format.json { head :ok }
end
end
end
diff --git a/railties/lib/rails/generators/rails/stylesheets/USAGE b/railties/lib/rails/generators/rails/stylesheets/USAGE
deleted file mode 100644
index 59e5495d0b..0000000000
--- a/railties/lib/rails/generators/rails/stylesheets/USAGE
+++ /dev/null
@@ -1,5 +0,0 @@
-Description:
- Copies scaffold stylesheets to public/stylesheets/.
-
-Examples:
- `rails generate stylesheets`
diff --git a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb b/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
deleted file mode 100644
index ce68443c39..0000000000
--- a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Rails
- module Generators
- class StylesheetsGenerator < Base
- def copy_stylesheets_file
- template "scaffold.css", "public/stylesheets/scaffold.css" if behavior == :invoke
- end
- end
- end
-end
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 964d59d84c..01fe6dda7a 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
@@ -19,30 +19,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
+ post :create, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
end
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should show <%= singular_table_name %>" do
- get :show, :id => @<%= singular_table_name %>.to_param
+ get :show, <%= key_value :id, "@#{singular_table_name}.to_param" %>
assert_response :success
end
test "should get edit" do
- get :edit, :id => @<%= singular_table_name %>.to_param
+ get :edit, <%= key_value :id, "@#{singular_table_name}.to_param" %>
assert_response :success
end
test "should update <%= singular_table_name %>" do
- put :update, :id => @<%= singular_table_name %>.to_param, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
+ put :update, <%= key_value :id, "@#{singular_table_name}.to_param" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, :id => @<%= singular_table_name %>.to_param
+ delete :destroy, <%= key_value :id, "@#{singular_table_name}.to_param" %>
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index 2c7b5bc048..bfd2a73aeb 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -67,13 +67,6 @@ module Rails
super || @@options.key?(name.to_sym)
end
- # static_asset_paths is a Hash containing asset_paths
- # with associated public folders, like:
- # { "/" => "/app/public", "/my_engine" => "app/engines/my_engine/public" }
- def static_asset_paths
- @@static_asset_paths ||= ActiveSupport::OrderedHash.new
- end
-
private
def method_missing(name, *args, &blk)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 591fd6f6bd..6d6e7f8b59 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -11,7 +11,7 @@
#
# Annotations are looked for in comments and modulus whitespace they have to
# start with the tag optionally followed by a colon. Everything up to the end
-# of the line (or closing ERb comment tag) is considered to be their text.
+# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
@@ -30,7 +30,7 @@ class SourceAnnotationExtractor
# Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
# and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
- # +.rxml+, +.rjs+, +.rhtml+, or +.erb+ are taken into account. The +options+
+ # +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
# hash is passed to each annotation's +to_s+.
#
# This class method is the single entry point for the rake tasks.
@@ -47,7 +47,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
- # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
def find(dirs=%w(app lib test))
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
@@ -55,7 +55,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dir+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
- # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
def find_in(dir)
results = {}
@@ -99,4 +99,4 @@ class SourceAnnotationExtractor
puts
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index af52014728..166d518f7c 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,6 +3,7 @@ $VERBOSE = nil
# Load Rails rakefile extensions
%w(
annotations
+ assets
documentation
framework
log
@@ -11,7 +12,6 @@ $VERBOSE = nil
routes
statistics
tmp
- railties
).each do |task|
load "rails/tasks/#{task}.rake"
end
diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake
new file mode 100644
index 0000000000..396ce728a1
--- /dev/null
+++ b/railties/lib/rails/tasks/assets.rake
@@ -0,0 +1,6 @@
+namespace :assets do
+ task :compile => :environment do
+ assets = Rails.application.config.assets.precompile
+ Rails.application.assets.precompile(*assets)
+ end
+end
diff --git a/railties/lib/rails/tasks/railties.rake b/railties/lib/rails/tasks/railties.rake
deleted file mode 100644
index 16703879cf..0000000000
--- a/railties/lib/rails/tasks/railties.rake
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace :railties do
- namespace :install do
- # desc "Copies missing assets from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2"
- task :assets => :rails_env do
- require 'rails/generators/base'
- Rails.application.initialize!
-
- to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip }
- app_public_path = Rails.application.paths["public"].first
-
- Rails.application.railties.all do |railtie|
- next unless to_load == :all || to_load.include?(railtie.railtie_name)
-
- if railtie.respond_to?(:paths) && (path = railtie.paths["public"].first) &&
- (assets_dir = railtie.config.compiled_asset_path) && File.exist?(path)
-
- Rails::Generators::Base.source_root(path)
- copier = Rails::Generators::Base.new
- Dir[File.join(path, "**/*")].each do |file|
- relative = file.gsub(/^#{path}\//, '')
- if File.file?(file)
- copier.copy_file relative, File.join(app_public_path, assets_dir, relative)
- end
- end
- end
- end
- end
- end
-end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index b9f7bdc2eb..41485c8bac 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -14,10 +14,14 @@ if defined?(Test::Unit::Util::BacktraceFilter) && ENV['BACKTRACE'].nil?
end
if defined?(MiniTest)
- require 'turn'
+ # Enable turn if it is available
+ begin
+ require 'turn'
- if MiniTest::Unit.respond_to?(:use_natural_language_case_names=)
- MiniTest::Unit.use_natural_language_case_names = true
+ if MiniTest::Unit.respond_to?(:use_natural_language_case_names=)
+ MiniTest::Unit.use_natural_language_case_names = true
+ end
+ rescue LoadError
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index b1eda71c7f..cd0646b8ed 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
s.add_dependency('rake', '>= 0.8.7')
s.add_dependency('thor', '~> 0.14.4')
s.add_dependency('rack-ssl', '~> 1.3.2')
- s.add_dependency('turn', '~> 0.8.2')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 044fd2a278..62697b1bf9 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1,5 +1,18 @@
require "isolation/abstract_unit"
+class ::MyMailInterceptor
+ def self.delivering_email(email); email; end
+end
+
+class ::MyOtherMailInterceptor < ::MyMailInterceptor; end
+
+class ::MyMailObserver
+ def self.delivered_email(email); email; end
+end
+
+class ::MyOtherMailObserver < ::MyMailObserver; end
+
+
module ApplicationTests
class ConfigurationTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -245,6 +258,80 @@ module ApplicationTests
assert_equal res, last_response.body # value should be unchanged
end
+ test "registers interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.interceptors = MyMailInterceptor
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors")
+ end
+
+ test "registers multiple interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.interceptors = [MyMailInterceptor, "MyOtherMailInterceptor"]
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors")
+ end
+
+ test "registers observers with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.observers = MyMailObserver
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers")
+ end
+
+ test "registers multiple observers with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.observers = [MyMailObserver, "MyOtherMailObserver"]
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ ActionMailer::Base
+
+ assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers")
+ end
+
+ test "valid timezone is setup correctly" do
+ add_to_config <<-RUBY
+ config.root = "#{app_path}"
+ config.time_zone = "Wellington"
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal "Wellington", Rails.application.config.time_zone
+ end
+
+ test "raises when an invalid timezone is defined in the config" do
+ add_to_config <<-RUBY
+ config.root = "#{app_path}"
+ config.time_zone = "That big hill over yonder hill"
+ RUBY
+
+ assert_raise(ArgumentError) do
+ require "#{app_path}/config/environment"
+ end
+ end
+
test "config.action_controller.perform_caching = false" do
make_basic_app do |app|
app.config.action_controller.perform_caching = false
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 018c2fa6bf..3ef06c7f25 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -8,6 +8,8 @@ DEFAULT_APP_FILES = %w(
Gemfile
Rakefile
config.ru
+ app/assets/javascripts
+ app/assets/stylesheets
app/controllers
app/helpers
app/mailers
@@ -22,8 +24,6 @@ DEFAULT_APP_FILES = %w(
lib/tasks
log
public/images
- public/javascripts
- public/stylesheets
script/rails
test/fixtures
test/functional
@@ -31,11 +31,9 @@ DEFAULT_APP_FILES = %w(
test/performance
test/unit
vendor
+ vendor/assets
vendor/plugins
- tmp/sessions
- tmp/sockets
tmp/cache
- tmp/pids
)
class AppGeneratorTest < Rails::Generators::TestCase
@@ -49,8 +47,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_application_controller_and_layout_files
run_generator
- assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag :all/
- assert_no_file "public/stylesheets/application.css"
+ assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/
+ assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/
+ assert_file "app/assets/stylesheets/application.css"
end
def test_invalid_application_name_raises_an_error
@@ -146,43 +145,40 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
- def test_prototype_and_test_unit_are_added_by_default
+ def test_jquery_and_test_unit_are_added_by_default
run_generator
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/rails.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/effects.js"
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js"
assert_file "test"
end
def test_javascript_is_skipped_if_required
run_generator [destination_root, "--skip-javascript"]
assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
- assert_file "public/javascripts/application.js"
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
+ assert_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_config_prototype_javascript_library
run_generator [destination_root, "-j", "prototype"]
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/effects.js"
- assert_file "public/javascripts/rails.js", /prototype/
+ assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/prototype.js"
+ assert_file "vendor/assets/javascripts/effects.js"
+ assert_file "vendor/assets/javascripts/dragdrop.js"
+ assert_file "vendor/assets/javascripts/controls.js"
+ assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
end
def test_config_jquery_javascript_library
run_generator [destination_root, "-j", "jquery"]
- assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
- assert_file "public/javascripts/application.js"
- assert_file "public/javascripts/jquery.js"
- assert_file "public/javascripts/rails.js", /jQuery/
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js", /jQuery/
end
def test_template_from_dir_pwd
@@ -216,6 +212,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_new_hash_style
+ run_generator [destination_root]
+ assert_file "config/initializers/session_store.rb" do |file|
+ if RUBY_VERSION < "1.9"
+ assert_match /config.session_store :cookie_store, :key => '_.+_session'/, file
+ else
+ assert_match /config.session_store :cookie_store, key: '_.+_session'/, file
+ end
+ end
+ end
+
+ def test_force_old_style_hash
+ run_generator [destination_root, "--old-style-hash"]
+ assert_file "config/initializers/session_store.rb" do |file|
+ assert_match /config.session_store :cookie_store, :key => '_.+_session'/, file
+ end
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb
new file mode 100644
index 0000000000..e99f0f092a
--- /dev/null
+++ b/railties/test/generators/assets_generator_test.rb
@@ -0,0 +1,26 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/assets/assets_generator'
+
+# FOXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub
+class AssetsGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ arguments %w(posts)
+
+ def test_assets
+ run_generator
+ assert_file "app/assets/javascripts/posts.js.coffee"
+ assert_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_skipping_assets
+ content = run_generator ["posts", "--no-stylesheets", "--no-javascripts"]
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_vanilla_assets
+ run_generator ["posts", "--no-javascript-engine", "--no-stylesheet-engine"]
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+end
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index be99dc068d..655d8ad450 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -37,6 +37,12 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/unit/helpers/account_helper_test.rb"
end
+ def test_invokes_assets
+ run_generator
+ assert_file "app/assets/javascripts/account.js"
+ assert_file "app/assets/stylesheets/account.css"
+ end
+
def test_invokes_default_test_framework
run_generator
assert_file "test/functional/account_controller_test.rb"
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 3c11c8dbaf..d20335ad95 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -95,41 +95,33 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_skipping_javascripts_without_mountable_option
run_generator
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
- assert_no_file "public/javascripts/controls.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/application.js"
+ assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_javascripts_generation
run_generator [destination_root, "--mountable"]
- assert_file "public/javascripts/rails.js"
- assert_file "public/javascripts/prototype.js"
- assert_file "public/javascripts/controls.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/application.js"
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/jquery.js"
+ assert_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_skip_javascripts
run_generator [destination_root, "--skip-javascript", "--mountable"]
- assert_no_file "public/javascripts/prototype.js"
- assert_no_file "public/javascripts/rails.js"
- assert_no_file "public/javascripts/controls.js"
- assert_no_file "public/javascripts/dragdrop.js"
- assert_no_file "public/javascripts/dragdrop.js"
+ assert_no_file "app/assets/javascripts/application.js"
+ assert_no_file "vendor/assets/javascripts/jquery.js"
+ assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
- def test_ensure_that_javascript_option_is_passed_to_app_generator
- run_generator [destination_root, "--javascript", "jquery"]
- assert_file "test/dummy/public/javascripts/jquery.js"
- end
-
- def test_ensure_that_skip_javascript_option_is_passed_to_app_generator
- run_generator [destination_root, "--skip_javascript"]
- assert_no_file "test/dummy/public/javascripts/prototype.js"
+ def test_config_prototype_javascript_library
+ run_generator [destination_root, "-j", "prototype", "--mountable"]
+ assert_file "app/assets/javascripts/application.js"
+ assert_file "vendor/assets/javascripts/prototype.js"
+ assert_file "vendor/assets/javascripts/effects.js"
+ assert_file "vendor/assets/javascripts/dragdrop.js"
+ assert_file "vendor/assets/javascripts/controls.js"
+ assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
end
def test_template_from_dir_pwd
@@ -153,6 +145,12 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_engine_in_full_mode
run_generator [destination_root, "--full"]
+ assert_file "app/assets/javascripts"
+ assert_file "app/assets/stylesheets"
+ assert_file "app/models"
+ assert_file "app/controllers"
+ assert_file "app/views"
+ assert_file "app/helpers"
assert_file "config/routes.rb", /Rails.application.routes.draw do/
assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
@@ -227,3 +225,4 @@ protected
silence(:stdout){ generator.send(*args, &block) }
end
end
+
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index d55ed22975..c7f45a807d 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -122,4 +122,22 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
ensure
Unknown::Generators.send :remove_const, :ActiveModel
end
+
+ def test_new_hash_style
+ run_generator
+ assert_file "app/controllers/users_controller.rb" do |content|
+ if RUBY_VERSION < "1.9"
+ assert_match /\{ render :action => "new" \}/, content
+ else
+ assert_match /\{ render action: "new" \}/, content
+ end
+ end
+ end
+
+ def test_force_old_style_hash
+ run_generator ["User", "--old-style-hash"]
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match /\{ render :action => "new" \}/, content
+ end
+ end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index df787f61ba..4b07f8bcbe 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -79,8 +79,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/helpers/product_lines_helper.rb"
assert_file "test/unit/helpers/product_lines_helper_test.rb"
- # Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_file "app/assets/javascripts/product_lines.js.coffee"
+ assert_file "app/assets/stylesheets/product_lines.css.scss"
end
def test_scaffold_on_revoke
@@ -110,8 +112,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/helpers/product_lines_helper.rb"
assert_no_file "test/unit/helpers/product_lines_helper_test.rb"
- # Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss", /&:visited/
+ assert_no_file "app/assets/javascripts/product_lines.js.coffee"
+ assert_no_file "app/assets/stylesheets/product_lines.css.scss"
end
def test_scaffold_with_namespace_on_invoke
@@ -184,8 +188,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/helpers/admin/roles_helper.rb"
assert_file "test/unit/helpers/admin/roles_helper_test.rb"
- # Stylesheets
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss", /&:visited/
+ assert_file "app/assets/javascripts/admin/roles.js.coffee"
+ assert_file "app/assets/stylesheets/admin/roles.css.scss"
end
def test_scaffold_with_namespace_on_revoke
@@ -216,8 +222,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/helpers/admin/roles_helper.rb"
assert_no_file "test/unit/helpers/admin/roles_helper_test.rb"
- # Stylesheets (should not be removed)
- assert_file "public/stylesheets/scaffold.css"
+ # Assets
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/admin/roles.js.coffee"
+ assert_no_file "app/assets/stylesheets/admin/roles.css.scss"
end
def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter
@@ -235,6 +243,34 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
end
+ def test_scaffold_generator_no_assets
+ run_generator [ "posts", "--no-assets" ]
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_stylesheets
+ run_generator [ "posts", "--no-stylesheets" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_file "app/assets/javascripts/posts.js.coffee"
+ assert_no_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_javascripts
+ run_generator [ "posts", "--no-javascripts" ]
+ assert_file "app/assets/stylesheets/scaffold.css.scss"
+ assert_no_file "app/assets/javascripts/posts.js.coffee"
+ assert_file "app/assets/stylesheets/posts.css.scss"
+ end
+
+ def test_scaffold_generator_no_negines
+ run_generator [ "posts", "--no-javascript-engine", "--no-stylesheet-engine" ]
+ assert_file "app/assets/stylesheets/scaffold.css"
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+
def test_scaffold_generator_outputs_error_message_on_missing_attribute_type
content = capture(:stderr) { run_generator ["post", "title:string", "body"]}
assert_match(/Missing type for attribute 'body'/, content)
diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb
deleted file mode 100644
index aaeb686daa..0000000000
--- a/railties/test/generators/stylesheets_generator_test.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'generators/generators_test_helper'
-require 'rails/generators/rails/stylesheets/stylesheets_generator'
-
-class StylesheetsGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
-
- def test_copy_stylesheets
- run_generator
- assert_file "public/stylesheets/scaffold.css"
- end
-
- def test_stylesheets_are_not_deleted_on_revoke
- run_generator
- run_generator [], :behavior => :revoke
- assert_file "public/stylesheets/scaffold.css"
- end
-end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index c5b1cb9a80..f2261540ca 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -90,6 +90,7 @@ module TestHelpers
end
module Generation
+ # Build an application by invoking the generator and going through the whole stack.
def build_app(options = {})
FileUtils.rm_rf(app_path)
FileUtils.cp_r(tmp_path('app_template'), app_path)
@@ -115,6 +116,8 @@ module TestHelpers
add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log'
end
+ # Make a very basic app, without creating the whole directory structure.
+ # This is faster and simpler than the method above.
def make_basic_app
require "rails"
require "action_controller/railtie"
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 20797a2b0c..7605984684 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1,8 +1,7 @@
require "isolation/abstract_unit"
require "railties/shared_tests"
-require 'stringio'
-require 'rack/test'
-require 'rack/file'
+require "stringio"
+require "rack/test"
module RailtiesTest
class EngineTest < Test::Unit::TestCase
@@ -188,6 +187,7 @@ module RailtiesTest
end
RUBY
+ require "rack/file"
boot_rails
env = Rack::MockRequest.env_for("/")
@@ -199,194 +199,6 @@ module RailtiesTest
assert_equal Rails.application.routes, env['action_dispatch.routes']
end
- test "it allows to set asset_path" do
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- end
- end
- RUBY
-
-
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index"
- end
- RUBY
-
- @plugin.write "app/controllers/foo_controller.rb", <<-RUBY
- class FooController < ActionController::Base
- def index
- render :index
- end
- end
- RUBY
-
- @plugin.write "app/views/foo/index.html.erb", <<-ERB
- <%= image_path("foo.png") %>
- <%= javascript_include_tag("foo") %>
- <%= stylesheet_link_tag("foo") %>
- ERB
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- add_to_config 'config.asset_path = "/omg%s"'
-
- boot_rails
-
- # should set asset_path with engine name by default
- assert_equal "/bukkits_engine%s", ::Bukkits::Engine.config.asset_path
-
- ::Bukkits::Engine.config.asset_path = "/bukkits%s"
-
- get("/bukkits/foo")
- stripped_body = last_response.body.split("\n").map(&:strip).join
-
- expected = "/omg/bukkits/images/foo.png" +
- "<script src=\"/omg/bukkits/javascripts/foo.js\" type=\"text/javascript\"></script>" +
- "<link href=\"/omg/bukkits/stylesheets/foo.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
- assert_equal expected, stripped_body
- end
-
- test "default application's asset_path" do
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index"
- end
- RUBY
-
- @plugin.write "app/controllers/foo_controller.rb", <<-RUBY
- class FooController < ActionController::Base
- def index
- render :inline => '<%= image_path("foo.png") %>'
- end
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- boot_rails
-
- get("/bukkits/foo")
- assert_equal "/bukkits/images/foo.png", last_response.body.strip
- end
-
- test "engine's files are served via ActionDispatch::Static" do
- add_to_config "config.serve_static_assets = true"
-
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- engine_name :bukkits
- end
- end
- RUBY
-
- @plugin.write "public/bukkits.html", "/bukkits/bukkits.html"
- app_file "public/app.html", "/app.html"
- app_file "public/bukkits/file_from_app.html", "/bukkits/file_from_app.html"
-
- boot_rails
-
- get("/app.html")
- assert_equal File.read(File.join(app_path, "public/app.html")), last_response.body
-
- get("/bukkits/bukkits.html")
- assert_equal File.read(File.join(@plugin.path, "public/bukkits.html")), last_response.body
-
- get("/bukkits/file_from_app.html")
- assert_equal File.read(File.join(app_path, "public/bukkits/file_from_app.html")), last_response.body
- end
-
- test "an applications files are given priority over an engines files when served via ActionDispatch::Static" do
- add_to_config "config.serve_static_assets = true"
-
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < ::Rails::Engine
- engine_name :bukkits
- end
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- @plugin.write "public/bukkits.html", "in engine"
-
- app_file "public/bukkits/bukkits.html", "in app"
-
- boot_rails
-
- get('/bukkits/bukkits.html')
-
- assert_equal 'in app', last_response.body.strip
- end
-
- test "shared engine should include application's helpers and own helpers" do
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- match "/foo" => "bukkits/foo#index", :as => "foo"
- match "/foo/show" => "bukkits/foo#show"
- match "/foo/bar" => "bukkits/foo#bar"
- end
- RUBY
-
- app_file "app/helpers/some_helper.rb", <<-RUBY
- module SomeHelper
- def something
- "Something... Something... Something..."
- end
- end
- RUBY
-
- @plugin.write "app/helpers/bar_helper.rb", <<-RUBY
- module BarHelper
- def bar
- "It's a bar."
- end
- end
- RUBY
-
- @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY
- class Bukkits::FooController < ActionController::Base
- def index
- render :inline => "<%= something %>"
- end
-
- def show
- render :text => foo_path
- end
-
- def bar
- render :inline => "<%= bar %>"
- end
- end
- RUBY
-
- boot_rails
-
- get("/foo")
- assert_equal "Something... Something... Something...", last_response.body
-
- get("/foo/show")
- assert_equal "/foo", last_response.body
-
- get("/foo/bar")
- assert_equal "It's a bar.", last_response.body
- end
-
test "isolated engine should include only its own routes and helpers" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
@@ -772,70 +584,6 @@ module RailtiesTest
assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path)
end
- test "ensure that engine properly sets assets directories" do
- add_to_config("config.action_dispatch.show_exceptions = false")
- add_to_config("config.serve_static_assets = true")
-
- @plugin.write "lib/bukkits.rb", <<-RUBY
- module Bukkits
- class Engine < ::Rails::Engine
- isolate_namespace Bukkits
- end
- end
- RUBY
-
- @plugin.write "public/stylesheets/foo.css", ""
- @plugin.write "public/javascripts/foo.js", ""
-
- @plugin.write "app/views/layouts/bukkits/application.html.erb", <<-RUBY
- <%= stylesheet_link_tag :all %>
- <%= javascript_include_tag :all %>
- <%= yield %>
- RUBY
-
- @plugin.write "app/controllers/bukkits/home_controller.rb", <<-RUBY
- module Bukkits
- class HomeController < ActionController::Base
- def index
- render :text => "Good morning!", :layout => "bukkits/application"
- end
- end
- end
- RUBY
-
- @plugin.write "config/routes.rb", <<-RUBY
- Bukkits::Engine.routes.draw do
- match "/home" => "home#index"
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- mount Bukkits::Engine => "/bukkits"
- end
- RUBY
-
- require 'rack/test'
- extend Rack::Test::Methods
-
- boot_rails
-
- require "#{rails_root}/config/environment"
-
- assert_equal File.join(@plugin.path, "public"), Bukkits::HomeController.assets_dir
- assert_equal File.join(@plugin.path, "public/stylesheets"), Bukkits::HomeController.stylesheets_dir
- assert_equal File.join(@plugin.path, "public/javascripts"), Bukkits::HomeController.javascripts_dir
-
- assert_equal File.join(app_path, "public"), ActionController::Base.assets_dir
- assert_equal File.join(app_path, "public/stylesheets"), ActionController::Base.stylesheets_dir
- assert_equal File.join(app_path, "public/javascripts"), ActionController::Base.javascripts_dir
-
- get "/bukkits/home"
-
- assert_match %r{bukkits/stylesheets/foo.css}, last_response.body
- assert_match %r{bukkits/javascripts/foo.js}, last_response.body
- end
-
private
def app
Rails.application
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index 3eb79d57c8..b2b18938ae 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -10,42 +10,16 @@ module RailtiesTest
@app ||= Rails.application
end
- def test_install_migrations_and_assets
- @plugin.write "public/javascripts/foo.js", "doSomething()"
+ def test_serving_sprockets_assets
+ @plugin.write "app/assets/javascripts/engine.js.coffee", "square = (x) -> x * x"
- @plugin.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration
- end
- RUBY
-
- app_file "db/migrate/1_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
- end
- RUBY
-
- add_to_config "ActiveRecord::Base.timestamped_migrations = false"
-
- Dir.chdir(app_path) do
- `rake bukkits:install`
- assert File.exists?("#{app_path}/db/migrate/2_create_users.rb")
- assert File.exists?(app_path("public/bukkits/javascripts/foo.js"))
- end
- end
-
- def test_copying_assets
- @plugin.write "public/javascripts/foo.js", "doSomething()"
- @plugin.write "public/stylesheets/foo.css", "h1 { font-size: 10000px }"
- @plugin.write "public/images/img.png", ""
-
- Dir.chdir(app_path) do
- `rake bukkits:install:assets --trace`
+ boot_rails
+ require 'rack/test'
+ require 'coffee_script'
+ extend Rack::Test::Methods
- assert File.exists?(app_path("public/bukkits/javascripts/foo.js"))
- assert_equal "doSomething()\n", File.read(app_path("public/bukkits/javascripts/foo.js"))
- assert File.exists?(app_path("public/bukkits/stylesheets/foo.css"))
- assert_equal "h1 { font-size: 10000px }\n", File.read(app_path("public/bukkits/stylesheets/foo.css"))
- assert File.exists?(app_path("public/bukkits/images/img.png"))
- end
+ get "/assets/engine.js"
+ assert_match "square = function(x) {", last_response.body
end
def test_copying_migrations